概述
定义
Spring框架的提出者是程序员Rod Johnson,他在2002年最早提出了这个框架的概念,随后创建了这个框架。Spring框架的目标是简化企业级Java应用程序的开发,通过提供一套全面的工具和功能,使开发者能够更加高效地构建高质量的应用程序。
-
Spring框架是一个开源的Java应用程序框架,广泛用于构建企业级Java应用程序。该框架提供了一种轻量级的编程模型,通过依赖注入(Dependency Injection)和面向切面编程(Aspect-oriented Programming)等技术,使得开发者可以更加方便地开发可扩展、模块化和松耦合的应用程序。Spring框架的核心思想主要围绕解决企业级编程开发中的复杂性,实现敏捷开发。
-
Spring框架的主要特点
- 轻量级
- 模块化
- 松耦合
- 可扩展性等
-
具体体现
- 控制反转(IoC)和依赖注入(DI):Spring框架采用了控制反转的设计原则,将对象的创建和依赖注入的过程交给框架来管理。这使得代码更加模块化和松耦合,提高了代码的可读性和可维护性。
- 面向切面编程(AOP):Spring提供了AOP的支持,可以将横切关注点(如日志、事务管理等)与业务逻辑进行解耦,使得代码更加简洁和清晰。
- 丰富的功能模块:Spring框架提供了许多功能模块,如数据访问、事务管理、Web开发、安全性等,使得开发者可以更加专注于业务逻辑的开发,而无需关注底层的实现细节。
- 支持声明式事务管理:Spring提供了事务管理的支持,可以通过声明式的方式来管理事务,而不需要编写繁琐的事务管理代码。
- 对代码的侵入小:Spring框架对代码的侵入较小,应用程序对Spring API的依赖比较少,方便整合老旧的项目。
-
它提供了多个模块,
- 核心容器(Core Container)
- 数据访问/集成(Data Access/Integration)
- Web支持
- AOP编程
- 消息传递和测试等
-
所用的设计模式
- 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
- 单例模式:Bean默认为单例模式。
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的
对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
重要模块
- Spring Core(核心模块):这是Spring框架的基础,提供了IoC(控制反转)和DI(依赖注入)的功能。它负责创建和管理应用中的对象,并通过配置文件或注解来定义对象之间的依赖关系。此外,Core模块还提供了许多核心工具类和辅助类,如资源管理器(ResourceLoader)和类型转换器(TypeConverter)。
- Spring AOP(面向切面编程):AOP模块使得开发者能够定义横切关注点,如日志、事务管理等,并将它们与业务逻辑分离。通过AOP,开发者可以更加灵活地管理这些横切关注点,提高代码的可维护性和重用性。
- Spring Web MVC:这是Spring框架的Web应用程序开发模块,它提供了构建Web应用程序所需的控制器、视图和模型等组件。Spring Web MVC通过注解和配置,简化了Web开发的流程,使得开发者可以更加高效地构建Web应用程序。
- Spring DAO与Spring ORM:这两个模块都是与数据库操作相关的。Spring DAO模块提供了对JDBC的支持,封装了JDBC的操作,使得开发者可以更方便地访问数据库。而Spring ORM模块则为各种- ORM框架(如Hibernate、JPA等)提供了集成支持,使得在Spring应用程序中使用ORM框架变得更加简单。
- Spring Context(上下文模块):该模块是Spring框架的核心,它提供了一个管理应用对象的环境。Spring Context模块不仅管理着对象的生命周期,还负责解析配置文件,将配置信息转化为对象间的依赖关系,为开发者提供了一个统一的、配置化的对象管理环境。
spring ioc
-
IOC的出现解决了什么问题:
- before 在传统的编程模型中,对象需要自行管理和获取其依赖,这导致了高耦合的代码结构。
- now 通过IoC,对象的创建、依赖关系的管理和销毁等过程都由IoC容器来负责。在IoC的思想(依赖注入(Dependency Injection,DI))下,对象的创建和依赖关系的管理都由IoC容器来负责。这样,对象的控制权就交给了IoC容器,从而实现了控制反转。开发者无需在代码中显式地创建和组装对象,直接通过配置文件或注解来定义对象及其依赖关系,然后由IoC容器在运行时动态地创建和组装这些对象,从而实现了对象之间的解耦。这种方式使得代码更加模块化、可测试和易于扩展。
- 优势:降低了代码之间的耦合度,提高了系统的可维护性和可扩展性。同时,它也使得测试更加容易,因为我们可以更容易地替换或模拟依赖对象。
-
控制反转的设计思想:
- 将原本在程序中手动创建对象的控制权,交给IOC容器来管理,并由IOC 容器完成对象的注入, 很大程度上简化应用的开发,从依赖中释放出来, 当我们创建一个对象的时候,只需要关注 配置文件。注解即可,无需关系是如何被创建的。
-
实现机制(工厂模式加反射机制)
interface Fruit { public abstract void eat(); } class Apple implements Fruit { public void eat(){ System.out.println("Apple"); } } class Orange implements Fruit { public void eat(){ System.out.println("Orange"); } } class Factory { public static Fruit getInstance(String ClassName) { Fruit f=null; try { f=(Fruit)Class.forName(ClassName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return f; } } class Client { public static void main(String[] a) { Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple"); if(f!=null){ f.eat(); } } }
-
设计底层原理
-
反射(Reflection):
- 反射是Java语言提供的一种强大的工具,它允许程序在运行时动态地获取类的信息,如类的属 性、方法、构造器等,并能通过反射机制创建对象、调用方法等。
- Spring IoC容器在底层利用Java反射机制来获取类的元数据,并根据这些元数据创建对象实例、设置属性值以及调用方法。
-
JavaBean规范:
- JavaBean是一种遵循特定规范的Java类,它通常具有私有字段、公共的getter和setter方法,以及无参构造器。
- Spring IoC容器在处理bean时,会遵循JavaBean规范,通过反射调用bean的setter方法来注入依赖。
-
BeanDefinition:
- Spring IoC容器内部使用BeanDefinition来描述一个bean的定义信息。这些信息包括bean的类名、作用域、生命周期回调、属性值等。
- 当容器启动时,它会解析配置文件或注解,将bean的定义信息转换为BeanDefinition对象,并存储在内部的数据结构中。
-
实例化Bean:
- 根据BeanDefinition中的类名信息,Spring IoC容器使用反射机制创建bean的实例。通过调用类的无参构造器来实现。
- 如果bean的类实现了特定的接口(如FactoryBean),容器还会按照特定的规则来创建实例。
-
依赖注入(Dependency Injection):
- 在bean实例创建之后,Spring IoC容器会根据BeanDefinition中的属性信息,通过反射调用bean的setter方法或构造器,将依赖的bean注入到当前bean中。这个过程称为依赖注入。
- Spring支持多种注入方式,如构造器注入、setter注入和字段注入等。
-
Bean的生命周期管理:
- Spring IoC容器不仅负责创建bean的实例,还负责管理bean的生命周期。它会在适当的时机调用bean的初始化方法和销毁方法,以执行必要的初始化逻辑和清理逻辑。
- 这些生命周期回调方法通常通过InitializingBean接口或特定的注解(如@PostConstruct和@PreDestroy)来定义。
-
AOP代理的创建:
- 如果bean被配置为使用AOP功能,Spring IoC容器会为该bean创建一个代理对象,并在代理对象中织入切面逻辑。
- 这个代理对象会实现bean所声明的接口,并在调用方法时执行额外的切面逻辑。
-
Spring IoC的Java设计底层原理主要依赖于Java反射机制和JavaBean规范,通过解析bean定义、创建实例、依赖注入以及管理生命周期等步骤来实现控制反转和依赖注入的功能。这些机制使得Spring IoC容器能够灵活地管理应用程序中的对象,降低耦合度,提高代码的可维护性和可扩展性。
- 应用场景
- 管理对象依赖关系:Spring IoC容器可以自动注入对象之间的依赖关系,避免手动创建对象并传递依赖,减少代码的耦合度,提高代码的可读性和可维护性。在Java EE企业应用开发中,IoC是解耦组件之间复杂关系的利器。
- 管理对象的生命周期:IoC容器能够管理对象的整个生命周期,包括创建、使用和销毁。当一个对象不再被使用时,IoC容器可以自动销毁它,释放资源,避免内存泄漏等问题。
- 管理配置信息:通过IoC容器,配置信息和代码得以分离,配置信息被集中管理,降低了代码和配置的耦合度,使得维护和修改更加便利。
- 管理事务:IoC容器可以自动管理事务,包括事务的开启、提交和回滚等,避免了手动管理事务的复杂性,提高了代码的可读性和可维护性。
- 实现AOP(面向切面编程):IoC容器可以方便地实现AOP,将横切关注点(如日志记录、事务管理等)与业务逻辑分离,进一步提高了代码的可读性和可维护性。
spring aop
- OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
- AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等
- AOP是OOP(面向对象编程)的补充,它关注于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)。
- AOP可以在不修改功能代码本身的前提下,使用运行时代理的技术对已有代码逻辑进行增强。它能够实现组件化、可插拔式的功能扩展,通过简单配置将功能增强到指定的切入点。
- AOP主要关注于将那些与业务逻辑无关的横切关注点(如日志、事务、权限等)从业务代码中分离出来,从而实现业务逻辑与非业务逻辑的解耦。
- 允许通过自定义的横切点进行模块化,将那些影响多个类的行为封装到重用的模块中, 在不修改源代码的情况下给程序动态统一添加额外功能。 比如: 日志输出,用之前的做法需要把日志的输出语句放在所有的类中,方法中,如果AOP就只需要将输出语句封装一个可重用的模块,以声明的形式完成日志的输出
在Spring AOP中,这些横切关注点被抽象成切面(Aspect),并通过配置或注解的方式将其应用到目标对象上。当目标对象的方法被调用时,Spring AOP会拦截这些方法的调用,并在调用前后自动执行切面的逻辑。这样,我们就可以在不修改业务代码的情况下为程序添加额外的功能。
Spring AOP里面的几个名词
- 切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在 Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
- 连接点(Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
- 通知(Advice):在AOP术语中,切面的工作被称为通知。
- 切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
- 引入(Introduction):引入允许我们向现有类添加新方法或属性。
- 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
- 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
- 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
- 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引
入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。 - 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目
标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。
Spring通知有哪些类型
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行
自定义的行为。
切面 Aspect
- Aspect由 pointcount 和 advice 组成,切面是通知和切点的结合。
- 在AOP中,切面是一个模块化的、可重用的单元,用于实现横切关注点。切面通过定义通知(Advice)和切点(Pointcut)来描述在程序中特定连接点(Join Point)处执行的行为。
- 通知(Advice)是切面中定义的具体操作,决定了切面功能的执行时间(例如方法执行前、方法执行后、方法抛出异常时等)
- 切点(Pointcut)决定了在哪些连接点上应用通知的定义。
依赖注入实现方式
- 依赖注入是时下最流行的IOC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。
- 接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。
- 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
- Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
bean对象
Spring beans概述
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。
-
Spring beans是Spring框架中的核心组件(形成应用程序基础的Java对象),它们是Spring IoC(控制反转)容器管理的对象或组件。
-
Spring容器负责初始化、装配和管理这些beans,通过容器中配置的元数据(如XML文件)来创建它们。
-
一个bean的定义包含了容器必知的所有配置元数据,如bean的标识、作用域、生命周期详情及其依赖等。
-
Spring容器可以负责管理bean的生命周期,包括实例化、属性赋值、初始化和销毁等阶段。
- singleton作用域的bean,Spring能够精确地知道该bean何时被创建、何时初始化完成以及何时被销毁;
- prototype作用域的bean,Spring只负责创建,当容器创建了bean的实例后,bean的实例就交给客户端代码管理,Spring容器将不再跟踪其生命周期。
在Spring开发中,beans通常用于数据访问、事务管理、Web开发等方面,是构建应用程序功能的关键部分。通过依赖注入和面向接口编程,Spring实现了松耦合,从而简化了企业级Java开发并促进了代码的可维护性和复用性。
Spring基于xml注入bean的几种方式
- Set方法注入;
- 构造器注入:
- 通过index设置参数的位置;
- 通过type设置参数类型;
- 静态工厂注入;
- 实例工厂;
作用域
- singleton 唯一bean实例,spring中的bean是单利的perototype 每次请求都会创建一个新的bean实例request 每一次http请求都会产生一个新的bean ,且只在当前http request内有效
- singleton : bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的SpringApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
如何在springioc中定义类的作用域
-
XML配置(scope属性)
<bean id="singletonBean" class="com.example.SingletonBean" scope="singleton"/> <bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype"/>
-
Java配置(使用@Scope注解)
@Configuration public class AppConfig { @Bean @Scope("singleton") public SingletonBean singletonBean() { return new SingletonBean(); } @Bean @Scope("prototype") public PrototypeBean prototypeBean() { return new PrototypeBean(); } }
-
自定义作用域
你还可以定义自己的作用域。这通常涉及到实现Scope接口,并在Spring配置中注册你的自定义作用域。但是,在大多数情况下,使用Spring提供的标准作用域就足够了。
-
在定义bean的作用域时,需要考虑应用程序的需求和上下文:
- 对于无状态的Web服务,使用singleton或prototype可能更合适
- 对于需要跟踪用户状态的Web应用程序,使用session或request可能更有意义。
线程安全的问题
- 原因
- 多个线程访问同一个对象的,该对象的非静态变量的写操作会存在线程安全的问题
- 解决
- bean对象中尽可能的避免定义 成员变量类中定义一个threadlocal成员变量,将需要的可变成员变量保存在threadlocal中
bean生命周期
- 实例化阶段:当springioc容器启动时候,根据bean的定义(读取配置文件或者xml文件),Spring容器创建bean的实例,自动装配bean之间的关系, 依赖关系,继承关系
-
执行过程
-
属性填充:Spring将配置文件中定义的属性值通过反射的方式注入到bean的对应属性中。
-
Aware接口回调:如果bean实现了特定的Aware接口(如BeanFactoryAware、ApplicationContextAware等),Spring会调用相应的setter方法,让bean感知其所在的上下文环境。
-
BeanPostProcessor处理:在bean初始化前后,Spring会调用注册的BeanPostProcessor的相应方法,允许开发者进行额外的处理(在bean生命周期的关键点上执行自定义逻辑)。
-
-
原理
- Spring IoC容器(如DefaultListableBeanFactory)根据bean定义(如RootBeanDefinition)创建bean的实例。
- 通过反射机制(如Class.newInstance()或Constructor.newInstance())创建bean的实例并填充对应的属性值
- Spring在bean的创建过程中,检查bean是否实现了某个Aware接口,并调用相应的方法(如setApplicationContext)。
- 在bean的初始化前后,Spring会调用所有注册的BeanPostProcessor的相应方法(如postProcessBeforeInitialization和postProcessAfterInitialization)
-
- 初始化阶段
- 调用bean的初始化方法,通常是通过配置文件中指定的init-method属性或注解@PostConstruct标注的方法。或者其他的初始化操作, 数据源的初始化,连接的建立
- 使用阶段
- 当bean的初始化完成,可以被使用, 通过 beanfactory 和applicationcontext 接口操作 bean
- 销毁阶段
- spring容器关闭或者不再使用的时候,调用bean的销毁方法,执行数据清理操作,关闭数据库连接,释放资源
- 通常是通过配置文件中指定的destroy-method属性或注解@PreDestroy标注的方法,进行资源释放等清理工作。
原理综述
- Spring对bean进行实例化
- Spring将值和bean的引用注入到bean对应的属性中
- 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法
- 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
- 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法
- 如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法
- 如果bean使用initmethod声明了初始化方法,该方法也会被调用
- 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法
- bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁
- 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用
涉及的设计模式
-
工厂设计
- ApplicationContext就是一个工厂对象,它根据Bean的ID从容器中获取对应的Bean实例。这就像是工厂根据订单生产并交付产品一样。
-
对象代理设计模式:AOP 功能实现
-
单例设计
- bean对象的管理:
-
用于确保一个类只有一个实例,并提供一个全局访问点。
-
Spring容器默认就是使用单例模式来管理Bean对象的,这意味着在Spring的IoC容器中,对于同一个Bean定义,不管通过容器查询多少次,获取到的都是同一个Bean实例。
-
举例:
public class SingletonExample { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取第一个Bean实例 SingletonBean bean1 = (SingletonBean) context.getBean("singletonBean"); bean1.setMessage("Hello bean1"); // 获取第二个Bean实例 SingletonBean bean2 = (SingletonBean) context.getBean("singletonBean"); bean2.setMessage("Hello bean2"); // 验证两个Bean实例是否是同一个对象 System.out.println(bean1 == bean2); // 应该输出 true,因为它们是同一个对象 System.out.println(bean1.getMessage()); // 应该输出 "Hello bean2",因为第二次设置的值覆盖了第一次的值 } }
-
- bean对象的管理:
-
适配器设计
AOP功能增强
bean的自动装配
Spring容器根据一定的规则自动为Bean之间建立依赖关系。这样,开发者无需显式地指定Bean之间的依赖关系,Spring会根据配置信息自动将合适的Bean注入到目标Bean中。
- 在XML中显式配置:通过配置
<bean>
标签的autowire属性来实现自动装配。autowire属性可以设置为byName或byType。当设置为byName时,Spring会自动在容器上下文中查找与自己对象的set方法后的值对应的bean id。当设置为byType时,Spring会自动在容器上下文中查找与自己对象的属性类型相同的bean。 - 在Java中显式配置:通过注解的方式实现自动装配。Spring提供了如@Autowired这样的注解,它可以标注在字段、构造函数或方法上,用于告诉Spring自动注入所需的Bean。
- 隐式的bean发现机制和自动装配:这是Spring自动装配的核心机制。它包括组件扫描(component scanning)和自动装配(autowiring)两个操作。组件扫描允许Spring自动发现应用上下文中所创建的Bean,而自动装配则自动满足Bean之间的依赖关系。
原理
基于Spring框架的依赖注入(Dependency Injection,DI)机制。Spring容器负责管理应用程序中的对象及其依赖关系,并在运行时将这些依赖关系自动注入到需要它们的bean中。
-
解析配置文件或注解:Spring容器首先解析应用程序的配置文件(如XML文件)或注解,以获取bean的定义及其属性信息。这些定义和属性描述了bean的类型、名称、依赖关系等。
-
创建Bean实例:基于解析得到的bean定义,Spring容器会创建相应的bean实例。这些实例可以是普通的Java对象,也可以是实现了特定接口的工厂对象。
-
检查自动装配注解或配置:对于标记了自动装配注解(如@Autowired)的字段、构造器或方法,Spring容器会检查其依赖关系。同时,容器也会检查是否有相关的自动装配配置,以确定如何进行依赖注入。
-
类型匹配和名称匹配:Spring容器会根据自动装配的规则(如byType或byName)进行依赖查找。
- 类型匹配,容器会查找与依赖项类型相匹配的bean;
- 名称匹配,容器会查找与依赖项名称相匹配的bean。
-
注入依赖项:一旦找到匹配的bean,Spring容器会将这些依赖项注入到目标bean中。注入可以通过字段注入、构造器注入或方法注入的方式完成。
- 字段注入,容器会直接设置字段的值;
- 构造器注入,容器会调用相应的构造器并传入依赖项作为参数;
- 方法注入,容器会调用特定的setter方法或带有@Autowired注解的其他方法。
-
完成Bean的初始化:在依赖注入完成后,Spring容器会调用bean的初始化方法(如实现了InitializingBean接口的afterPropertiesSet方法或配置文件中指定的init-method),完成bean的初始化过程。
基于Java的Spring注解配置
使用Java注解来定义和管理Spring应用程序中的Bean及其依赖关系,而不是传统的基于XML的配置方式。这种配置方式使得代码更加简洁、易读和易于维护。
-
@Configuration:这个注解用于定义配置类,表明该类是一个Spring配置类,可以替代传统的XML配置文件。在这个配置类中,你可以使用**@Bean**注解来定义Bean。
@Configuration public class MyConfiguration { @Bean public MyBean myBean() { return new MyBean(); } }
-
@Component:这是一个通用注解,表明一个类是一个Spring组件。它可以被Spring扫描并作为Bean注册到Spring容器中。
-
@Controller:这个注解用于标识一个类作为Spring MVC的控制器,用于处理HTTP请求。
@Controller public class TestController { @RequestMapping("/test") public String test(Map<String, Object> map) { return "hello"; } }
-
@RestController:这是Spring 4之后引入的注解,用于创建RESTful Web服务。它相当于@Controller和@ResponseBody的结合,返回的数据默认以JSON格式呈现。
-
@Service:这个注解用于标识服务层的组件,通常包含业务逻辑代码。
-
@Repository:这个注解用于标识数据访问层的组件,通常用于注解数据访问对象(DAO)或MyBatis的Mapper接口。
-
@Autowired:这个注解用于自动装配Bean,它可以标注在字段、构造函数或方法上,Spring会自动将匹配的Bean注入到标注的位置。
-
@Value:这个注解用于注入值,可以标注在字段上,用于从配置文件或环境变量中注入值。
@Value("${config.name}") private String name;
@Autowired注解自动装配的过程
-
定义Bean
- 在Spring容器中定义Bean。可以通过XML配置文件或使用Java配置类(带有@Configuration和@Bean注解的类)来完成。每个Bean都是应用程序中的一个组件,可以是服务、数据访问对象、控制器等。
-
使用@Autowired注解:
-
在需要注入依赖的类中,使用@Autowired注解来标记字段、构造函数或方法。Spring容器会查找匹配的Bean并将其注入到相应的位置。
- 字段注入: 直接在字段上使用@Autowired,Spring会自动为该字段赋值。
- 构造函数注入: 在构造函数上使用@Autowired,Spring会通过构造函数参数的类型或名称来注入依赖。
- 方法注入: 在方法上使用@Autowired,如果方法有参数,Spring会在IOC容器中查找同类型参数为其传值,并自动执行该方法。
-
-
启动Spring容器:
- Spring容器启动时(例如,通过调用AnnotationConfigApplicationContext或ClassPathXmlApplicationContext的构造函数),会扫描所有的类路径
- 查找带有@Component、@Service、@Repository、@Controller等注解的类,并将它们注册为Bean。
- 同时,也会查找带有@Autowired注解的位置。
-
自动装配过程:
- 类型匹配: Spring首先会根据@Autowired标注的位置(字段、构造函数或方法参数)的类型来查找匹配的Bean。如果容器中存在一个唯一的Bean与所需类型匹配,Spring就会将该Bean注入到相应的位置。
- 名称匹配: 如果根据类型找到多个匹配的Bean,或者明确指定了按名称装配(通过@Qualifier注解),Spring会尝试根据Bean的名称进行匹配。它会查找与@Qualifier指定的名称或@Autowired标注位置的字段名、方法参数名相匹配的Bean。
- 注入: 一旦找到合适的Bean,Spring就会将其注入到@Autowired标注的位置。对于字段注入,Spring会直接设置字段的值;对于构造函数注入,Spring会使用匹配的Bean来调用构造函数;对于方法注入,Spring会调用方法并传递匹配的Bean作为参数。
-
异常处理:
- 如果在自动装配过程中找不到匹配的Bean,Spring会抛出一个异常(通常是NoSuchBeanDefinitionException),指示无法完成注入。这通常意味着存在配置错误或缺少必要的Bean定义。
- 注意:从Spring 4.3开始,@Autowired注解不再是必须的,因为Spring可以通过构造函数注入来自动检测组件,只要组件的构造函数只有一个。这被称为构造函数的隐式自动装配。
@Autowired 和 @Resource的区别
- 用途:构造函数、成员变量、Setter方法
- 区别
- @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
- @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
@Component 和 @Bean区别
@Component注解主要用于自动扫描和注册类为Bean,而@Bean注解则提供了更大的灵活性,允许我们自定义Bean的创建过程。
-
@Component
-
@Component是一个通用性的注解,用于将一个类标记为Spring上下文中的一个组件。
-
当Spring容器启动时,它会自动扫描并实例化所有带有@Component注解的类(这些类可以被视为Spring容器管理的Bean),并将其注册到Spring上下文中。
-
@Component注解可以用于任何类,包括控制器、服务、DAO等。被@Component注解的类会成为Spring上下文中的一个bean,并可以被其他bean通过依赖注入的方式使用。
-
-
@Bean
- @Bean注解通常用于方法上,其作用是将当前方法的返回值对象注册到Spring容器中。
- 当使用@Bean注解时,我们实际上是在告诉Spring:“这个方法会返回一个对象,我希望你能够管理这个对象并将其作为Bean注册到容器中”。
- @Bean注解提供了更大的灵活性,因为它允许我们控制Bean的创建过程,甚至可以在创建Bean时执行一些自定义的逻辑。
- 可以用于在引用第三方库的类并装配到spring容器中
@Qualifier 注解有什么作用
-
当存在多个相同类型的Bean可供注入时,Spring容器可能无法确定要注入的具体Bean对象。在这种情况下,@Qualifier注解可以帮助我们指定具体要注入的Bean。
-
具体来说,@Qualifier注解可以与@Autowired或@Resource等注解一起使用,通过指定一个自定义的限定符(qualifier)标识符来消除歧义。这个限定符可以是字符串或其他注解类型。当Spring容器在自动装配过程中遇到多个匹配的Bean时,它会根据@Qualifier注解中指定的限定符来确定要注入的Bean。
-
例如,假设我们有一个接口MessageService和两个实现类EmailService和SMSService。在某个类中需要依赖MessageService时,由于存在多个实现类,Spring容器无法自动确定要注入哪个实现类。这时,我们可以使用@Autowired和@Qualifier注解结合使用,通过@Qualifier注解指定具体的实现类名称或自定义的限定符,从而解决这种类型冲突。
@Qualifier注解在Spring框架中用于解决依赖注入时的歧义性问题,通过指定具体的Bean名称或自定义限定符来消除歧义,从而确保正确的Bean被注入到需要的位置。
@Component, @Controller, @Repository, @Service 有何区别?.
@Component是通用的组件注解,@Controller用于MVC控制层,@Repository用于数据访问层,而@Service则用于业务逻辑层。
- @Component:这是Spring框架中最基本的注解,用于标识一个普通的Spring组件。它表示一个通用的组件,可以用于任何层级(如业务逻辑层、数据访问层等)。被@Component注解的类会被Spring容器自动扫描并注册为Bean,从而可以被其他组件通过依赖注入的方式使用。
- @Controller:这个注解专门用于标识Spring MVC中的控制器组件。它用于处理用户请求,通常标注在控制层。控制器负责接收来自前端的请求,调用相应的业务逻辑处理,并返回响应给前端。
- @Repository:这个注解用于标识数据访问对象(DAO),即用于与数据库或其他持久化机制进行交互的类。它的主要作用是将一个类标记为一个数据访问对象,并告诉Spring框架需要为这个类创建实例,将其纳入到Spring的上下文中进行统一管理。此外,@Repository还具有异常转译的功能,可以将数据访问层的异常转换为Spring的数据访问异常体系,方便在业务层进行处理和统一管理。
- @Service:这个注解用于标识业务逻辑层的组件。服务层通常用于处理主要的业务逻辑,如调用DAO层进行数据访问、进行业务逻辑处理、调用其他服务等。@Service注解的类也会被Spring容器自动扫描并注册为Bean,并且通常是单例的。
在Spring框架中,如果有多个对象需要注入到某个类中,通常有以下几种处理方式:
-
使用@Autowired注解配合@Qualifier注解
-
场景:当Spring容器中存在多个相同类型的Bean时,
- 使用@Qualifier注解来指定要注入的Bean的名称。
- @Autowired注解用于自动装配Bean
-
示例:
@Autowired @Qualifier("specificBeanName") private MyBean myBean;
-
-
使用@Primary注解
-
场景:如果大多数情况下你想注入某个特定的Bean,但偶尔需要注入其他Bean,你可以使用@Primary注解。
- 当存在多个相同类型的Bean时,优先选择标注了@Primary的Bean进行注入。
-
示例:
@Component @Primary public class MyPrimaryBean implements MyBean { // ... } @Component public class MyAlternativeBean implements MyBean { // ... }
-
注意:如果没有使用@Qualifier明确指定,Spring会默认注入MyPrimaryBean。
-
-
使用Java配置
-
示例:通过Java配置类来明确地定义和注册Bean,并通过方法注入来指定要注入的Bean。
- 根据具体的业务逻辑来决定注入哪个MyBean实例到MyConsumer中。
@Configuration public class AppConfig { @Bean public MyBean myPrimaryBean() { return new MyPrimaryBean(); } @Bean public MyBean myAlternativeBean() { return new MyAlternativeBean(); } @Bean public MyConsumer myConsumer(MyBean myBean) { return new MyConsumer(myBean); // 这里的myBean将是通过某种逻辑确定的 } }
-
-
使用构造函数注入
-
示例:通过类的构造函数来注入多个对象。
- 这种方式可以提供更好的依赖可见性和不变性。
- 明确地指定要注入的Bean,并使用@Qualifier来区分它们。
public class MyConsumer { private final MyBean myPrimaryBean; private final MyBean myAlternativeBean; @Autowired public MyConsumer(@Qualifier("myPrimaryBean") MyBean myPrimaryBean, @Qualifier("myAlternativeBean") MyBean myAlternativeBean) { this.myPrimaryBean = myPrimaryBean; this.myAlternativeBean = myAlternativeBean; } }
-
使用List或Map注入
- 场景:需要注入多个相同类型的Bean,并且不关心它们的具体名称,你可以使用List或Map来注入这些Bean。
- Spring会自动将所有匹配的Bean注入到这些集合中。
-
示例:
@Autowired private List<MyBean> myBeans; // 或者 @Autowired private Map<String, MyBean> myBeanMap;
-
@Required 注解有什么作用
-
@Required注解用于标记那些必须通过依赖注入进行设置的bean属性。表示被注解的属性在配置Bean的时候是必录的,且这些属性必须在XML配置文件或相应的配置类中正确配置。反之,Spring容器会抛出一个BeanInitializationException异常。
- @Required注解可以应用于bean属性的setter方法上,也可以直接应用于字段上。当Spring容器初始化Bean的时候,会检查被注解的属性是否已经被正确配置。如果没有正确配置,Spring容器将抛出BeanInitializationException异常,提示开发者缺少了必需的属性。
-
注意:从Spring 2.5版本开始,@Required注解已经不再是必须的,因为Spring容器本身就能够检测到bean属性是否通过依赖注入进行了设置。
@Autowired 注解有什么作用
@Autowired可以自动将匹配的bean注入到被标注的字段、构造器或方法中,从而简化了依赖注入的配置过程。
-
自动装配bean:Spring容器在初始化bean时,会自动查找和注入匹配的bean。这避免了在XML配置文件中显式地声明每个bean的依赖关系,减少了配置文件的复杂性。
-
支持字段、构造器和方法注入:@Autowired注解可以应用于字段、构造器或setter方法上。当应用于字段时,Spring会在bean创建后自动注入匹配的bean;当应用于构造器或setter方法时,Spring会使用这些方法来注入依赖。
-
类型匹配和名称匹配:Spring默认按照类型(byType)进行自动装配。如果容器中存在多个相同类型的bean,Spring会尝试按照名称(byName)进行匹配。如果字段、构造器参数或方法参数的名称与bean的名称一致,Spring会优先注入该bean。
-
可选性和必需性:@Autowired注解有一个required属性,默认为true。当required为true时,表示该依赖是必需的,如果Spring容器中没有找到匹配的bean,则会抛出异常。当required为false时,表示该依赖是可选的,如果没有找到匹配的bean,则不会进行注入。
-
与其他注解的协同使用:@Autowired通常与@Qualifier注解一起使用,以解决多个相同类型bean的注入问题。@Qualifier注解可以指定要注入的bean的名称。
spring事务
Spring事务的实现方式和实现原理
-
实现方式
-
编程式事务管理
-
在代码中显式地管理事务的边界,包括事务的开启、提交和回滚。
-
通过使用 TransactionTemplate 或直接在代码中调用 PlatformTransactionManager 的方法来实现。
PlatformTransactionManager txManager = ... // 获取事务管理器 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = txManager.getTransaction(def); try { // 执行数据库操作 txManager.commit(status); } catch (Exception ex) { txManager.rollback(status); throw ex; }
-
-
声明式事务管理
-
通过注解或 XML 配置来声明事务的边界和属性
-
基于注解的事务管理
使用 @Transactional 注解在方法或类上声明事务的边界。Spring AOP 会在运行时为这些方法创建代理,并在方法调用前后插入事务管理的代码。@Service @Transactional public class MyService { // 业务逻辑方法 }
-
基于 XML 的事务管理
通过配置 XML 文件来声明事务管理器、事务属性以及哪些方法需要事务支持。
-
-
-
-
实现原理
-
AOP 代理
- Spring 通过 AOP 框架为需要事务管理的方法创建代理对象。当这些方法被调用时,实际上是代理对象在起作用。
- 代理对象会在方法调用前后插入事务管理的代码。
-
事务管理器
- PlatformTransactionManager 是 Spring 事务管理的核心接口,它定义了事务管理的基本操作,如获取事务状态、提交和回滚事务等。
- 不同的数据源(如 JDBC、Hibernate、JPA 等)需要不同的实现类来管理事务。
-
事务属性
- TransactionDefinition 接口定义了事务的属性,如传播行为、隔离级别、超时时间、只读标志和回滚规则等。这些属性可以通过注解或 XML 配置来设置。
-
事务同步
- Spring 使用 TransactionSynchronizationManager 来管理事务的同步。
- 当一个事务被开启时,Spring 会将事务状态绑定到当前线程上,确保在同一个线程中的多个数据库操作都在同一个事务中执行。
-
事务传播行为
- @Transactional 注解中的 propagation 属性定义了事务的传播行为。
- 例如:
- REQUIRED 表示当前方法必须运行在事务中,如果当前没有事务,就新建一个事务
- SUPPORTS 表示当前方法不需要事务上下文,但是如果当前存在事务,那么它就在这个事务中运行。
-
Spring的事务传播行为
-
Spring事务的传播行为定义了当一个事务方法被另一个事务方法调用时,这个被调用的事务方法应该如何应对。Spring提供了七种主要的事务传播行为:
- PROPAGATION_REQUIRED:
- 如果当前存在事务,就使用当前事务。
- 如果当前没有事务,则新建一个事务。
- Spring事务传播行为的默认设置。
- PROPAGATION_SUPPORTS:
- 支持当前事务,如果当前存在事务,就加入该事务。
- 如果当前不存在事务,就以非事务方式执行。
- PROPAGATION_MANDATORY:
- 支持当前事务,如果当前存在事务,就加入该事务。
- 如果当前不存在事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:
- 创建新事务,无论当前是否存在事务,都创建新事务。
- 如果当前有事务,当前事务会挂起。
- PROPAGATION_NOT_SUPPORTED:
- 以非事务方式执行操作。
- 如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:
- 以非事务方式执行。
- 如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:
- 如果当前存在事务,则在嵌套事务内执行。
- 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作,即新建一个事务。
- PROPAGATION_REQUIRED:
-
相关案例:
-
场景描述:假设一个在线银行系统,其中有两个主要的服务方法:transferMoney 和 recordLog。
- transferMoney 方法负责在两个账户之间转账
- recordLog 方法负责记录转账日志。
-
实现分析:
- 需要确保两个方法在一个事务中执行,以确保数据的一致性和完整性。
- 考虑到日志记录是一个相对轻量级的操作,可以考虑与转账操作分开处理。
-
代码实现:
@Service public class BankService { @Autowired private AccountRepository accountRepository; @Autowired private LogRepository logRepository; @Transactional public void transferMoney(String fromAccountId, String toAccountId, BigDecimal amount) { Account fromAccount = accountRepository.findById(fromAccountId).orElse(null); Account toAccount = accountRepository.findById(toAccountId).orElse(null); if (fromAccount != null && toAccount != null) { fromAccount.deduct(amount); toAccount.credit(amount); // 使用REQUIRES_NEW传播行为,确保log记录操作在一个新的事务中执行 recordLog(fromAccountId, toAccountId, amount, "Transfer successful"); } else { throw new RuntimeException("Invalid account IDs"); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void recordLog(String fromAccountId, String toAccountId, BigDecimal amount, String message) { Log log = new Log(); log.setFromAccountId(fromAccountId); log.setToAccountId(toAccountId); log.setAmount(amount); log.setMessage(message); logRepository.save(log); } }
-
解析
- transferMoney 方法被标记为 @Transactional,意味着它会在一个事务中执行。如果在转账过程中发生任何异常,整个事务(包括从账户扣款和向账户存款)都会被回滚,确保数据的一致性。
- recordLog 方法也被标记为 @Transactional,但使用了 propagation = Propagation.REQUIRES_NEW。这意味着无论 transferMoney 方法是否已经在事务中执行,recordLog 方法都会在一个新的事务中执行。这样,即使记录日志的操作失败,也不会影响 transferMoney 方法中的转账操作。
-
spring 的事务隔离
-
SQL 标准定义了四种事务隔离级别,它们从低到高依次是:
-
读未提交(Read Uncommitted)
- 允许一个事务读取另一个未提交事务的修改。
- 可能出现脏读、不可重复读和幻读。
- 大多数数据库系统不会将其作为默认的隔离级别,因为它的风险较高。
-
读已提交(Read Committed)
- 只允许读取已提交的数据。
- 可以避免脏读,但可能出现不可重复读和幻读。
- 大多数数据库系统的默认隔离级别。
-
可重复读(Repeatable Read)
- 在同一事务内,多次读取同一数据返回的结果是一样的。
- 可以避免脏读和不可重复读,但可能出现幻读。
- MySQL 的 InnoDB 存储引擎在默认设置下使用此隔离级别。
-
串行化(Serializable)
- 强制事务串行执行,而不是并行执行。
- 可以避免脏读、不可重复读和幻读。
- 性能开销最大,因为它完全阻止了并发访问。
-
-
在 Spring 中,你可以通过 @Transactional 注解的 isolation 属性来指定事务的隔离级别。例如:
@Transactional(isolation = Isolation.READ_COMMITTED) public void someServiceMethod() { // ... }
面试题
beanFactory 和 applicationContext 有什么区别
-
基本定义与功能:
- BeanFactory是Spring框架中的核心接口之一,它定义了Spring容器的基本行为,负责管理Bean的生命周期、配置元数据和依赖注入。其主要功能是提供Bean的创建、配置、初始化和销毁等基本操作,并可以根据配置文件或注解来创建并管理Bean实例。
- ApplicationContext是BeanFactory的子接口,除了包含BeanFactory的所有功能外,还在国际化支持、资源访问(如URL和文件)、事件传播等方面进行了良好的支持。因此,ApplicationContext提供了更完整的框架功能。
-
加载方式:
- BeanFactory采用延时加载的方式,即在容器启动时不会注入Bean,而是在需要使用Bean的时候,才会对该Bean进行加载实例化。
- ApplicationContext则在容器启动的时候,一次性创建所有的Bean,所以运行的时候速度相对BeanFactory比较快。
-
创建方式:
- BeanFactory通常是以编程的方式创建的。
- ApplicationContext则是以声明的方式创建的,并且是自动注册的。
BeanFactory和FactoryBean的区别
-
定义及功能
- BeanFactory是Spring框架中的一个核心接口,它定义了Spring容器的基本行为,负责管理Bean的生命周期、配置元数据和依赖注入。BeanFactory的主要作用是提供Bean的创建、配置、初始化和销毁等基本操作。
- 而FactoryBean是Spring提供的一种特殊的Bean,它的主要功能是创建和返回其他Bean对象。
-
作用
- BeanFactory的主要作用是提供Bean的创建、配置、初始化和销毁等基本操作。
- 进行功能扩展,当配置某个Bean实现了FactoryBean接口时,该Bean返回的对象不是FactoryBean本身,而是FactoryBean#getObject()方法返回的对象。可以在getObject方法里定制创建逻辑。
-
应用
- BeanFactory可以根据配置文件或注解来创建并管理Bean实例,并提供了各种方法来获取和操作Bean实例。在配置方式上,BeanFactory通常通过XML配置文件或Java注解进行配置,定义和管理Bean对象。此外,BeanFactory支持延迟初始化,即只有在需要时才创建Bean实例。
- FactoryBean负责产生其他Bean实例。也即当我们从IOC容器中获取一个FactoryBean时,我们得到的是它创建的那个Bean的实例,而不是FactoryBean的实例本身。
总结来说,BeanFactory是Spring框架中用于创建和管理Bean的工厂类,而FactoryBean则是用于创建和返回其他Bean对象的特殊Bean。
哪几种方法可以完成依赖注入
依赖注入(Dependency Injection)是一种软件设计模式,用于减少类之间的耦合性,提高代码的可维护性和可测试性。它的原理是将类的依赖关系从类内部移出来,在外部将依赖对象注入到类中。
- 构造方法注入:
- 被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IOC容器)知道它需要哪些依赖对象。
- IOC容器会检查被注入对象的构造方法,取得其所需要的依赖对象列表,进而为其注入相应对象。
- 可以保证Bean的不变性,从而提高程序的稳定性和可维护性。
- setter方法注入:
- 当前对象只需要为其依赖对象所对应的属性添加setter方法。
- IOC容器通过此setter方法将相应的依赖对象设置到被注入对象。
- 可以实现对Bean的动态配置,从而使得程序更加灵活和可扩展。
- 接口注入:
- 被注入对象如果想要IOC容器为其注入依赖对象,就必须实现某个接口。
- 这个接口提供一个方法,用来为被注入对象注入依赖对象。
- IOC容器通过接口方法将依赖对象注入到被注入对象中去。
- 这种方式相对复杂一些,因为它要求被注入对象实现特定的接口。
构造函数注入setter 注入接口注入
- 构造函数注入 适用于那些必须在Bean创建时就注入依赖的情况,确保Bean的不可变性。
- Setter方法注入 提供了更大的灵活性,允许在Bean创建后的任何时间点注入依赖。
- 接口注入 不是一种推荐的注入方式,但在某些高级或集成场景中可能有用。通常,更推荐使用构造函数注入或Setter方法注入。
JDK动态代理和CGLIB动态代理的区别
-
实现方式:
- JDK动态代理是通过实现被代理接口来创建代理类的,它要求被代理的对象必须实现一个或多个接口。
- CGLIB动态代理则是通过继承被代理类来创建代理类的,因此它不受限于接口,可以代理任意类。
-
支持的代理对象类型:
- JDK动态代理依赖于接口,因此它只能代理实现了接口的类。
- CGLIB动态代理可以代理任何类,无论其是否实现了接口。
-
性能:创建代理类的过程
- JDK动态代理的创建过程相对简单,因此其性能略好一些。
- CGLIB动态代理在创建代理类时需要使用ASM字节码操作库来修改字节码,这使得其创建过程相对较慢,性能较差。然而,在某些情况下,如大规模的方法拦截和增强等场景,CGLIB代理可能更适合,因为它能够代理那些没有实现任何接口的目标对象。
-
应用场景:
- 需要代理实现了接口的类且对性能有一定要求的情况下,JDK动态代理可能是更好的选择
- 需要代理任意类,或需要进行更复杂的方法拦截和增强的情况下,CGLIB动态代理可能更为合适。