通过
6
万多字的篇幅向你全方位解析Spring IoC
。
IoC
IoC
,全称Inversion of Control
,即控制反转。相较于传统编程模式,IoC
实现了对控制流的反转,即:
- 传统编程模式: 用户代码 -> 库/框架 -> 用户代码。对于传统编程模式,每次调用库/框架时都会将控制权从用户代码转移到库/框架中,然后库/框架在执行结束后再将控制权返回到用户代码。
IoC
编程模式: 库/框架 -> 用户代码 -> 库/框架。对于IoC
编程模式,一般我们会将我们的行为类插入到库/框架中,库/框架则会在相应的执行点将控制权转移到用户代码,然后用户代码在执行结束后再将控制权返回到库/框架。
在Spring
文档是这样阐述的:
It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
大致意思就是:对象通过构造参数、Factory
方法参数或Setter
方法参数指定的依赖项,容器会在后面bean
创建的时候进行注入(通过使用类的直接构造或类似于服务定位器模式的机制来控制其依赖项的实例化或位置),这基本反转了bean
的创建流程(因此得名,控制反转)。
正因为此,IoC
也被称为dependency injection
(DI
),即依赖注入。但是严格来说它们并不等价,DI
仅仅是IoC
众多实现方式中的一种,它的实现方式还包括回调(callbacks
)、调度程序(schedulers
)、事件循环(event loops
)和模板方法(the template method
)等。
总的来说,所谓IoC
控制反转就是,"it calls me rather me calling the framework"
。
IoC
一词并非起源于Spring
,而是起源于1988
年《Object-Oriented Programming》
杂志中Johnson
和Foote
的一篇论文《Designing Reusable Classes》
。关于
IoC
、DI
等更多详情可以阅读以下资料:
Container
在Spring
中,IoC
容器主要负责对bean
进行实例化、配置和组装,其中对于这些bean
需要我们通过XML
或Java
代码等方式声明在Metadata
配置中以让容器进行读取并获得指导。
在
Spring
中,IoC
容器所管理的对象称之为bean
,它是由IoC
容器实例化、组装和管理的对象。
下图从宏观视角来给出Spring
是如何工作的。
+
|
| Your Business Objects(POJOs)
|
v
+----------+-----------+
| |
+----------------->+ The Spring Container |
Configuration | |
Metadata +----------+-----------+
|
| produces
|
v
+-----------+-------------+
| |
| Fully configured system |
| Ready for Use |
| |
+-------------------------+
从上图中显示,在IoC
容器中通过结合业务对象类和Metadata
配置来创建和初始化容器类(例如,ApplicationContext
),以此来得到一个配置完全并可执行的系统或应用。
需要注意,容器本身并不会对
Metadata
配置的格式作任何限制,即对于存储的配置格式是LDAP
、RDBMS
还是XML
都没有任何限制。所有这些不同格式的配置,容器都是通过扩展类(例如XmlBeanDefinitionReader
、AutowiredAnnotationBeanPostProcessor
等)进行解析,并存储为BeanDefinition
来进行处理,这也大幅增加了容器的灵活性和可扩展性。
ApplicationContext
Spring
使用了ApplicationContext
表示IoC
容器,它会读取Metadata
配置中的指令对bean
进行实例化、配置和组装。因此,在将业务对象类和Metadata
配置注入到容器ApplicationContext
后,我们就可以使用它来获取对应的bean
实例,例如:
// create and configure beans
ApplicationContext context = ...;
// retrieve configured instance
IocService service = context.getBean("iocService", IocService.class);
在开发过程中一个比较好的做法是通过依赖注入的方式来配置应用实例。换句话说,我们应该通过容器
Push
配置到相应的地方;而不是主动从容器中Pull
相应的配置,比如主动通过ApplicationContext
来获取对象(以任何形式)。
BeanFactory
实际上,ApplicationContext
中bean
相关的能力都源自于BeanFactory
(继承自BeanFactory
),所以我们也能通过BeanFactory
完成bean
的实例化、配置和组装。即:
// create and configure beans
BeanFactory factory = ...;
// retrieve configured instance
IocService service = factory.getBean("iocService", IocService.class);
本质上,BeanFactory
相当于是应用组件的注册中心,持有并集中了大量的应用组件的配置信息(例如,持有大量的bean
定义,每个bean
定义都具有独一无二的名字)。当然,我们也可以将BeanFactory
看作是IoC
容器的"客户端",通过这个"客户端"我们可以完成会IoC
容器访问。
ApplicationContext
vs. BeanFactory
虽说ApplicationContext
与BeanFactory
都能完成bean
的实例化、配置和组装,但是我们一般更加推荐使用ApplicationContext
。因为ApplicationContext
不但包含了BeanFactory
所有的能力,而且它还会将几种特殊的bean
(特别是后置处理器BeanPostProcessor
)加入到容器中激活Spring
的一些特性(例如,注解的处理或者AOP
代理的处理)。
当然,如果想在bean
的处理流程上得到完全的控制,我们也可以直接使用BeanFactory
。对于这种情况,默认是不会有上述ApplicationContext
的特性,而如果要加入这些特性则需要额外将这些特殊的bean
配置进BeanFactory
,例如:
-
将特殊的
BeanPostProcessor
应用到BeanFactory
// BeanFactory的默认实现 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(new FileSystemResource("beans.xml")); // now register any needed BeanPostProcessor instances factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); // now start using the factory
-
将特殊的
BeanFactoryPostProcessor
应用到BeanFactory
// BeanFactory的默认实现 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(new FileSystemResource("beans.xml")); // bring in some property values from a Properties file // PropertySourcesPlaceholderConfigurer属于BeanFactoryPostProcessor的一种实现 PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory);
不难看出,将特殊的bean
逐个注册到BeanFactory
中是一件十分烦杂的事情,所以这就是为什么Spring
更加推荐使用ApplicationContext
。
BeanFactoryPostProcessor
和BeanPostProcessor
对于许多容器的功能特性是必不可少的,例如注解处理和AOP
代理等功能都是通过它来实现的。
最后,这里列出了BeanFactory
和ApplicationContext
提供功能的对比:
Feature |
BeanFactory |
ApplicationContext |
---|---|---|
Bean instantiation/wiring |
Yes |
Yes |
Integrated lifecycle management |
No |
Yes |
Automatic BeanPostProcessor registration |
No |
Yes |
Automatic BeanFactoryPostProcessor registration |
No |
Yes |
Convenient MessageSource access (for internationalization) |
No |
Yes |
Built-in ApplicationEvent publication mechanism |
No |
Yes |
Spring
官方推荐: 除非有特别充分的理由,否则推荐使用ApplicationContext
。
Metadata
根据上文所述,Metadata
配置主要提供于容器读取并指导容器完成对bean
的实例化、配置和组装。而在配置Metadata
时,我们是可以通过XML
和Java
的方式完成配置的。
通过XML
配置Metadata
通过XML
的方式对Metadata
进行配置则需要将需要声明的bean
配置为<bean/>
元素(包含在<beans/>
中),如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
下面列出一些常见的XML
标签和属性:
标签 | 描述 | 更多 | ||||||
<beans> | <beans>用于包含需要声明的<bean> | - | ||||||
default-init-method | default-init-method指定当前<beans>下的全部<bean>的默认初始化方法 | - | ||||||
default-destroy-method | default-destroy-method指定当前<beans>下的全部<bean>的默认销毁方法 | - | ||||||
default-lazy-init | default-lazy-init指定当前<beans>下的全部<bean>都为lazy-init | - | ||||||
default-autowire-candidates | default-autowire-candidates指定当前<beans>下的全部<bean>只有匹配其设置的一个/多个模式时才能成为自动装配的候选者 | - | ||||||
<bean> | <bean>表示要声明的bean | - | ||||||
id | id属性是一个唯一表示bean的字符串 | - | ||||||
name | name属性用于指定bean的名字 | - | ||||||
class | class属性使用全限定类名来指定bean的类型 | - | ||||||
factory-bean | factory-bean属性用于指定构建bean的实例工厂方法所在bean的名称,不过注意class属性需要设置为空 | - | ||||||
factory-method | factory-method属性用于指定构建bean的静态/实例工厂方法的名称,如指定实例工厂方法则需要配合factory-bean属性 | - | ||||||
depends-on | depends-on属性显式指定在初始化当前bean前需要强制初始化一个或多个bean。另外,depends-on属性在基于初始化的顺序上还可以控制了其销毁的顺序。 | - | ||||||
lazy-init | lazy-init属性用于标识当前bean为延迟初始化,容器会在它第一次被请求时才创建bean实例,而不是在启动时初始化。但是需要注意,当这个lazy-init的bean是单例且作为非lazy-init的bean依赖时,容器还是会在启动时创建它。 | - | ||||||
autowire | autowire属性用于指定bean为自动装配模式 | - | ||||||
init-method | init-method属性用于指定bean的初始化方法(无参无返回值的方法) | - | ||||||
destroy-method | destroy-method属性用于指定bean的销毁方法(无参无返回值的方法) | - | ||||||
scope | scope属性用于指定bean的作用域 | - | ||||||
<constructor-arg> | <constructor-arg>标签表示构造器注入所需指定的参数 | - | ||||||
ref | ref属性用于指定构造参数所引用的bean实例id,如果构造参数没有存在歧义可以仅仅按照参数的顺序进行声明 | - | ||||||
type | type属性用于指定构造参数的类型,需要配置value或ref属性使用 | - | ||||||
index | index属性用于指定构造参数的索引,对于解析多个存在歧义的参数可通过index属性来解决 | - | ||||||
name | name属性用于指定构造参数的名字,对于解析多个存在歧义的参数可通过name属性来解决 | - | ||||||
value | value属性用于指定构造参数的值(基本类型),一般配合type、index和name属性使用 | - | ||||||
<property> | <property>标签表示bean属性 | - | ||||||
ref | ref属性用于指定属性所引用的bean实例id | - | ||||||
type | type属性用于指定属性的类型,需要配置value或ref属性使用 | - | ||||||
name | name属性用于指定属性的名字 | - | ||||||
value | value属性用于指定属性的值(基本类型),一般配合type和name属性使用 | - |
通过Java
配置Metadata
通过Java
的方式对Metadata
进行配置主要是通过注解声明。如下所示:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
下面列出一些常见的Java
注解和属性:
注解 | 描述 | 更多 |
---|---|---|
@Configuration |
@Configuration 是一个类级别注解,它用于标识当前类是bean 定义的配置类(容器会扫描@Configuration 注解类中声明的bean ,并且@Configuration 类本身也会作为bean 被注册到容器中)。 |
跳转 |
@Component |
@Component 是一个类级别注解,它用于标识当前类是一个可被容器检测到的bean 组件(容器会扫描@Component 注解类中声明的bean )。 |
跳转 |
@Controller |
@Controller 是一个类级别注解,它是由@Component 注解衍生出的专用于标识Controller 层的注解,本质上还是@Component 。 |
跳转 |
@Service |
@Service 是一个类级别注解,它是由@Component 注解衍生出的专用于标识Service 层的注解,本质上还是@Component 。 |
跳转 |
@Repository |
@Repository 是一个类级别注解,它是由@Component 注解衍生出的专用于标识Repository 层的注解,本质上还是@Component 。 |
跳转 |
@Bean |
@Bean 是一个方法级别注解,它用于将当前方法返回的对象声明为bean ,即将当前方法返回的对象注册到容器中。需要注意,它需要与@Configuration 或@Component 联合使用。 |
跳转 |
@Autowired |
@Autowired 是一个构造器级别、方法级别、参数级别和字段级别的注解,它用于标识当前修饰的元素使用Spring 依赖注入机制进行自动装配。 |
跳转 |
@Primary |
@Primary 是一个类级别和方法级别的注解,它用于标识当前bean 作为依赖注入到其他实例时具有更高的优先级(当单值依赖在执行自动装配时,如果存在多个候选者,@Primary 注解所修饰候选者具有更高的优先级)。 |
跳转 |
@Scope |
@Scope 是一个类级别和方法级别的注解,它用于指定当前bean 的作用域。需要注意,它仅在具体bean 类(@Component 组件)或工厂方法(@Bean 方法)上有效。 |
跳转 |
@Qualifier |
@Qualifier 是一个类级别、方法级别、参数级别和字段级别的注解,它用于在自动装配时对候选者进行标识(指定限定符)。 |
跳转 |
@Resource |
@Resource 为JSR-250 规范的注解,可修饰类、方法和字段,主要用于标识需要自动装配的属性。其中,我们可通过name 属性指定需注入的bean 名称,默认它会使用字段名或Setter 方法属性名作为需注入的bean 名称。 |
跳转 |
@Value |
@Value 是一个方法级别、参数级别和字段级别的注解,它用于指定被修饰元素的值表达式。 |
跳转 |
@PostConstruct |
@PostConstruct 为JSR-250 规范的注解,可修饰方法,主要用于标识bean 的初始化方法。 |
跳转 |
@PreDestroy |
@PreDestroy 为JSR-250 规范的注解,可修饰方法,主要用于标识bean 的销毁方法。 |
跳转 |
@Inject |
@Inject 为JSR-330 规范的注解,可修饰构造器、方法和字段,主要用于标识当前修饰的元素使用Spring 依赖注入机制进行自动装配(作用与@Autowired 相同)。 |
跳转 |
@Named |
@Named 为JSR-330 规范的注解,可修饰类,主要用于标识当前类是一个可被容器检测到的bean 组件,作用与@Component 相同。 |
跳转 |
@ManagedBean |
@ManagedBean 为JSR-250 规范的注解,可修饰类,主要用于标识当前类是一个可被容器检测到的bean 组件,作用与@Component 相同。 |
跳转 |
Bean
通过上述步骤即把配置读取到容器中,然后容器会再进一步生成相应的bean
实例。无论是通过哪种方式,对于注册到容器中的bean
都会被定义为BeanDefinition
,其主要包含以下属性:
属性 | 描述 |
---|---|
Class |
Class 属性表示当前bean 的类型。 |
Name |
Name 属性表示当前bean 的名称。 |
Scope |
Scope 属性表示当前bean 的作用域。 |
Constructor arguments |
Constructor arguments 属性表示当前bean 的构造参数。 |
Properties |
Properties 属性表示当前bean 的属性。 |
Autowiring mode |
Autowiring mode 属性表示当前bean 的自动装配模式。 |
Lazy initialization mode |
Lazy initialization mode 属性表示当前bean 是否延迟初始化。 |
Initialization method |
Initialization method 属性表示当前bean 的初始化方法。 |
Destruction method |
Destruction method 属性表示当前bean 的销毁方法。 |
关于BeanDefinition
,它主要有三种实现,分别是RootBeanDefinition
、ChildBeanDefinition
和GenericBeanDefinition
:
BeanDefinition 实现 |
描述 |
---|---|
RootBeanDefinition |
RootBeanDefinition 是一个标准的BeanDefinition ,在配置阶段中可作为一个独立的BeanDefinition 。 |
ChildBeanDefinition |
ChildBeanDefinition 是一个具有继承关系的BeanDefinition ,它会继承父BeanDefinition 的属性和覆盖父BeanDefinition 方法。例如,对于init 方法、destroy 方法和静态工厂方法ChildBeanDefinition 是会覆盖继承的父BeanDefinition (如有),而depends on 、autowire mode 、dependency check 、singleton 、lazy init 等属性则是直接忽略父BeanDefinition ,直接以ChildBeanDefinition 的为准。 |
GenericBeanDefinition |
GenericBeanDefinition 是一个具有一站式功能的标准BeanDefinition 。除了与其他BeanDefinition 具有相同的功能外,它还能通过parentName 属性灵活/动态地设置其父BeanDefinition 。在大多数场景中,GenericBeanDefinition 都可以有效地替代ChildBeanDefinition 。 |
在
Spring 2.5
引入了GenericBeanDefinition
后,我们应该将GenericBeanDefinition
作为以程序方式注册bean
的首选类。因为通过GenericBeanDefinition
我们可以动态地定义父BeanDefinition
(通过parentName
属性),而不是将角色"硬编码"为RootBeanDefinition
或者ChildBeanDefinition
。当然,如果能提前确定父/子关系的话,我们也可以使用RootBeanDefinition
/ChildBeanDefinition
。
这样,IoC
容器完成了将Metadata
配置转换为(含以上属性(不限于))BeanDefinition
的过程。在完成转换后,IoC
容器就可以根据BeanDefinition
的定义来创建并初始化bean
实例了。
不难看出,
BeanDefinition
实际上类似于Class
,我们对bean
的所有配置都已经定义到BeanDefinition
里,然后再通过BeanDefinition
来创建对象实例。
Bean
实例化
对于Bean
的实例化,我们可以通过构造器和工厂方法两种方式进行创建,而不论是构造器还是工厂方法都可以通过XML
和Java
进行声明。
一般,
IoC
容器会通过BeanDefinition
中的class
属性(强制)来找到需要实例化的类。但是,class
属性所标识的类不一定就是需要实例化的bean
,我们可以将它看作是用于实例化bean
的类,可以表示bean
本身的类型,也可以表示bean
工厂方法所在的类。
通过构造器实例化
通过XML
&&构造器配置实例
在XML
配置中,如果没有特别指定,容器就会通过构造器来创建bean
实例。
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
通过Java
&&构造器配置实例
在Java
配置中,通过@Configuration
和@Component
注解及其衍生注解(典型的有: @Controller
、@Service
、@Repository
)声明的类会被当作bean
注册到容器中。对于这种两种方式,IoC
也会使用它们的构造器来创建bean
实例。
@Configuration
public class MyConfig {}
@Configuration
一般用于标识bean
定义的来源类。
@Component
public class MyComponent {}
@Component
一般用于标识Spring
的通用组件类。为了起到区分作用,Spring
通过元注解组合@Component
的方式衍生出三个更专用的注解,分别是持久层的@Repository
、服务层的@Service
和表示层的@Controller
。通过这种方式的区分,我们可以对业务层级进行更针对性地处理,例如对不同层级作增强处理(AOP
)。另外,官方可能也会在未来的发行版中对相应的注解添加额外的语义(例如,对于@Repository
注解提供持久层异常自动转换的支持(已支持))。
通过工厂方法实例化
通过XML
&&工厂方法配置实例
在XML
配置中,我们可以在<bean>
标签中设置factory-method
属性来指定创建bean
实例的工厂方法,其中工厂方法又分为静态工厂方法和实例工厂方法。
-
静态工厂方法
对于静态工厂方法,我们需要在
<bean>
标签中设置class
属性来指定工厂方法的所在类,并通过factory-method
属性来指定工厂方法。<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
-
实例工厂方法
对于实例工厂方法,我们需要在
<bean>
标签中设置factory-bean
属性来指定bean
实例,并通过factory-method
属性来指定工厂方法(注意,class
属性需留空)。上述使用实例工厂方法创建
bean
实例,可以简单的概括为使用现有bean
实例的非静态方法来创建bean
实例。<!-- the factory bean, which contains a method called createInstance() --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <!-- the bean to be created via the factory bean --> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } }
通过这种方式,我们也可以在一个工厂类中声明多个实例工厂方法,如下例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> <bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } }
通过Java
&&工厂方法配置实例
对于Java
配置的方式(使用工厂方法来创建bean
实例),在Spring
官方文档中并没有明确展开。但,实际上笔者在阅读源码时发现了通过Java
的方式也能使用工厂方法来创建bean
实例,并且它也区分了静态工厂方法和实例工厂方法。
-
实例工厂方法
在
Java
配置的方式中,Spring
会把@Configuration
和@Component
注解类中的标识了@Bean
的实例方法当作是实例工厂方法来处理。即,使用这种方式创建的bean
实例都是通过实例工厂方法来完成的。@Configuration public class MyConfig { @Bean public MyService myService() { return new MyService(); } }
@Component public class MyComponent { @Bean public MyService myService() { return new MyService(); } }
-
静态工厂方法
与实例工厂方法类似,
Spring
会把@Configuration
和@Component
注解类中的标识了@Bean
的静态方法当作是静态工厂方法来处理。即,使用这种方式创建的bean
实例都是通过静态工厂方法来完成的。@Configuration public class MyConfig { @Bean public static MyService myService() { return new MyService(); } }
@Component public class MyComponent { @Bean public static MyService myService() { return new MyService(); } }
抛开静态工厂方法与实例工厂方法的术语,对于上述通过@Bean
来声明bean
实例的方式有两个关键的区别:
-
@Configuration
类与@Component
类中声明@Bean
方法的区别当
@Bean
方法声明在@Configuration
类中时,我们称之为Full
模式;而当@Bean
方法声明在@Component
类中时,我们称之为Lite
模式。在Full
模式下,@Bean
方法之间的相互调用会被重定向到IoC
容器中,从而避免bean
实例的重复创建,这也被称为inter-bean references
;而在Lite
模式下,@Bean
方法之间的相互调用并不支持inter-bean references
,它们之间的相互调用仅仅会被当作是普通的工厂方法调用(标准的Java
调用),即会再次创建对象实例。需要注意,因为
Full
模式是通过CGLIB
动态代理的方式来完成的,所以在配置@Bean
工厂方法时不能将其设置为private
和final
。与此相反,Lite
模式由于不需要使用CGLIB
动态代理,不但去除了这样的限制,而且也降低了服务的启动时间和加载时间。对于
Lite
模式,除了可以将@Bean
方法声明在@Component
注解类中实现外,我们还可以使用@Component
、@ComponentScan
、@Import
、@ImportResource
或者将@Bean
方法声明在普通Java
类(无任何注解标注)中来实现。而在Spring5.2
后新增了属性proxyBeanMethods
(proxyBeanMethods
表示是否开启代理,默认为true
表示开启),当proxyBeanMethods=false
时也表示Lite
模式,即@Configuration(proxyBeanMethods = false)
。关于
Full
模式和Lite
模式的更多详情可以阅读以下资料: -
静态
@Bean
方法与实例@Bean
方法的区别当使用静态
@Bean
方法时,它可以在它所在的@Configuration
或@Component
类尚未实例化前完成实例化,这对我们通过@Bean
来声明后置处理器(BeanFactoryPostProcessor
或BeanPostProcessor
)时特别有用,因为这避免了实例化后置处理器时提前触发了其他部分的实例化从而导致不可预知的异常。需要注意,由于
Full
模式是通过CGLIB
动态代理的方式来完成的(只对实例方法有效),所以对于@Bean
静态方法的调用并不会被IoC
容器所拦截(即使在@Configuration
类中也不会),即对@Bean
静态方法的调用仅仅被当作是标准的Java
方法(直接返回一个独立的对象实例)。从另一方面来看,由于@Bean
静态方法属于Lite
模式,无需遵守Full
模式下的相关限制(例如,不能是private
或final
),所以我们可以在任何我们觉得合适的地方声明@Bean
静态方法。关于静态
@Bean
方法与实例@Bean
方法的更多详情可阅读以下资料:
通过FactoryBean
实例化
另外,对于一些构建起来比较复杂的bean
实例我们可以通过FactoryBean
来完成。对于FactoryBean
,在使用上我们需要将它声明为bean
,这样Spring
就会将FactoryBean
及其FactoryBean#getObject
方法返回的对象都注册到IoC
容器中。其中,对于FactoryBean
指定或默认生成的名称是其FactoryBean#getObject
所创建的bean
实例名称,而FactoryBean
实例本身的名称则需在指定或默认生成的名称前加上前缀&
,即&beanName
。
public class MyServiceFactoryBean implements FactoryBean {
/**
* 表示当前FactoryBean生成对象是否为单例
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* 表示当前FactoryBean生成对象类型
*/
@Override
public Class getObjectType() {
return MyService.class;
}
/**
* 方法返回FactoryBean需要创建的对象实例
*/
@Override
public Object getObject() {
return new MyServiceImpl();
}
}
需要注意,对于FactoryBean#isSingleton
方法标识当前FactoryBean#getObject
生成对象是否为单例的用法需要与FactoryBean
实例本身的作用域结合使用。例如,如果需要令FactoryBean#getObject
所生成的对象为单例作用域,那么我们不但需要让FactoryBean#isSingleton
方法返回true
,而且还需让FactoryBean
实例本身作用域为单例作用域;否则Spring
都会按照多例作用域Prototype
来处理。
关于
FactoryBean
的更多细节可阅读以下资料:
Bean
依赖
在Spring
中,我们可以使用依赖注入(dependency injection
,简称DI
)的方式定义bean
依赖(推荐),这样IoC
容器就会在创建bean
的时候将这些依赖注入到实例中,即使用构造器参数、工厂方法参数或实例属性(对象实例化后被设置)对其依赖完成注入。
在软件工程中,依赖注入(
dependency injection
)是一种设计模式,通过分离对象构造和对象使用来实现程序上的松耦合,即一个对象(或方法)对于它所需要(依赖)的其它对象(或方法)应该采用接收的方式,而不是在内部创建它们。这样,对象(或方法)本身就不需要关注它所需要的对象(或方法)是如何构造的了。关于
DI
的更多详情可阅读以下资料:
依赖注入
在使用上,我们可以通过构造器参数、工厂方法参数、Setter
方法等完成bean
的依赖注入。而由于构造器注入与工厂方法注入几乎等价,所以下面将注入方式分为两种,即基于构造器注入和基于Setter
方法注入。
通过构造器注入依赖
对于构造器注入,我们首先需要将相应的依赖声明为构造器参数,即:
public class SimpleBean {
private final OneInjectBean oneInjectBean;
private final TwoInjectBean twoInjectBean;
public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
然后,我们再将对应的bean
实例及其依赖进行标记,以让IoC
容器完成实例化和依赖注入。下面我们分别从XML
的方式和Java
的方式展开描述。
通过XML
&&构造器配置依赖
在XML
配置中,我们可以使用标签<constructor-arg>
来标记构造参数及需要被注入的依赖。
<beans>
<bean id="simpleBean" class="com.example.SimpleBean">
<constructor-arg ref="oneInjectBean"/>
<constructor-arg ref="twoInjectBean"/>
</bean>
<bean id="oneInjectBean" class="com.example.OneInjectBean"/>
<bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
</beans>
需要注意,此处仅使用了ref
属性来引用对应的依赖是因为它们之间并没有引起歧义,如果参数之间存在歧义或者构造参数顺序与XML
配置不一致时,就需要添加type
属性或index
属性。
更多详情可阅读:
通过Java
&&构造器配置依赖
在Java
配置中,我们可以使用注解@Autowired
或@Inject
来标记需要使用依赖注入的构造器。
@Component
public class SimpleBean {
private final OneInjectBean oneInjectBean;
private final TwoInjectBean twoInjectBean;
@Autowired
public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
需要注意:
- 自
Spring4.3
起,如果需要实例化的bean
只定义了一个构造器,则可以不用在构造器上添加@Autowired
注解或@Inject
注解。但是,如果需要实例化的bean
存在多个构造器,且其中没有默认构造器(无参),这时我们就需要至少在一个构造器上标记@Autowired
注解。- 自
Spring4.3
起,Spring
才开始支持@Configuration
类的构造器注入。
而对于使用工厂方法来实例化bean
(即通过@Bean
注解标记的方法)则只需要在方法参数上指定需要注入的依赖即可。
@Configuration
public class MyConfig {
@Bean
public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
return new SimpleBean(oneInjectBean, twoInjectBean);
}
}
更多详情可阅读:
通过Setter
注入依赖
对于Setter
方法注入,我们首先需要在bean
中定义对应依赖的Setter
方法,即:
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
public void setOneInjectBean(OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
this.twoInjectBean = twoInjectBean;
}
}
这样,Spring
就可以在bean
实例化后通过调用对应的Setter
方法来完成依赖注入。同样的,对于Setter
方法的依赖注入也可以分为XML
和Java
两种方式进行配置,下面笔者将各自展开描述。
通过XML
&&Setter
配置依赖
在XML
配置中,我们可以在<bean>
标签中添加<property>
标签来标记需要注入的属性,这样IoC
容器就会在bean
实例化后调用属性相应的Setter
方法完成依赖注入。
<beans>
<bean id="simpleBean" class="com.example.SimpleBean">
<property name="oneInjectBean" ref="oneInjectBean"/>
<property name="twoInjectBean" ref="twoInjectBean"/>
</bean>
<bean id="oneInjectBean" class="com.example.OneInjectBean"/>
<bean id="twoInjectBean" class="com.example.TwoInjectBean"/>
</beans>
与构造器注入类似,<property>
标签也支持通过type
和value
属性来指定对应的属性类型和属性值。
更多详情可阅读:
通过Java
&&Setter
配置依赖
在Java
配置中,我们可以使用注解@Autowired
、@Inject
或@Resource
在属性对应的Setter
方法上进行标记,以完成Setter
方法的依赖注入。
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Autowired
public void setOneInjectBean(OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
@Autowired
public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
this.twoInjectBean = twoInjectBean;
}
}
更多详情可阅读:
构造器注入 vs. Setter
注入
Spring
团队提倡我们应该更多地通过构造器注入,因为通过构造器注入你可以将依赖对象声明为不可变对象,并且可以确保注入的依赖不为空。另外,如果通过这种方式进行注入而造成大量的构造参数也可以提前提醒我们这里存在bad code smell
,因为这意味着当前类可能存在太多的职责,我们应该进行重构来适当的分离类的职责以更好地解决问题。而对于Setter
方法的注入,Spring
团队则建议将它用于一些可选的/可切换的依赖项上,这样我们就可以很轻易地对其依赖进行重新配置或重新注入。另外,在配置这些可选依赖项时可在类中分配合理的默认值,否则就必须在使用依赖项的地方进行非空检查。总的来说,我们可以混合使用构造器注入与Setter
方法注入,其中对于一些必须的依赖项通过构造器来强制注入,而对于一些可选依赖项则通过Setter
方法来进行注入。
更多详情可阅读:
扩展特性
自动装配
上文提及用于依赖注入的@Autowired
注解实际上是用来标记Spring
的自动装配模式的,所谓自动装配即Spring
通过查询IoC
容器(ApplicationContext
)中的bean
来帮我们自动解析并装配相应的bean
依赖。
对于bean
的自动装配,Spring
提供了四种不同的模式让我们选择:
模式 | 说明 |
---|---|
no |
(默认)没有自动装配。在这种模式下bean 依赖必须通过ref 属性来指定。 |
byName |
通过属性名字自动装配。Spring 会查询与属性名字相同的bean 进行自动装配。例如,当bean 被配置为通过byName 模式完成自动装配,那么对于它名为master 的属性,Spring 会从IoC 容器中查询出同样名为master 的bean ,若存在则将它自动装配到对应属性中。 |
byType |
通过属性类型自动装配。Spring 会查询与属性类型相同的bean 进行自动装配,但是前提是容器中正好存在一个与它属性类型相匹配的bean 。如果容器中存在多个类型相匹配的bean 则会抛出异常;而如果没有类型相匹配的bean 则不会发生任何事情(即属性未被设置)。 |
constructor |
类似于byType 模式,但适用于构造器参数。与byType 模式不同的是,如果容器中没有一个bean 与构造器参数类型相匹配的话,则会抛出异常。 |
其中,在byType
或constructor
自动装配的模式下,我们可以自动装配数组和集合。在这种情况下IoC
容器中所有与之类型相匹配的bean
都会被注入到对应的数组或集合中。
同样在使用上我们可以使用XML
的方式和Java
的方式进行配置,即:
-
通过
XML
配置自动装配在
XML
配置中,我们可以在<bean/>
标签中指定autowire
属性来设置bean
的自动装配模式,例如:public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } public void setOneInjectBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } public void setTwoInjectBean(TwoInjectBean twoInjectBean) { this.twoInjectBean = twoInjectBean; } }
如果我们使用
byName
模式,则可以像如下这样配置:<beans> <bean id="simpleBean" class="com.example.SimpleBean" autowire="byName"/> <bean id="oneInjectBean" class="com.example.OneInjectBean"/> <bean id="twoInjectBean" class="com.example.TwoInjectBean"/> </beans>
如果我们使用
byType
模式,则可以像如下这样配置:<beans> <bean id="simpleBean" class="com.example.SimpleBean" autowire="byType"/> <bean id="oneInjectBean" class="com.example.OneInjectBean"/> <bean id="twoInjectBean" class="com.example.TwoInjectBean"/> </beans>
如果我们使用
constructor
模式,则可以像如下这样配置:<beans> <bean id="simpleBean" class="com.example.SimpleBean" autowire="constructor"/> <bean id="oneInjectBean" class="com.example.OneInjectBean"/> <bean id="twoInjectBean" class="com.example.TwoInjectBean"/> </beans>
-
通过
Java
配置自动装配与
XML
配置不同,通过Java
配置我们只需要在构造器、Setter
方法或者属性上标记@Autowired
注解即可,例如:需要注意,通过这种方式只能使用
byType
模式进行自动装配。如果我们以构造器的方式配置自动装配,则可以像如下这样配置:
@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Autowired public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
如果我们以
Setter
方法的方式自动装配,则可以像如下这样配置:@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Autowired public void setOneInjectBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } @Autowired public void setTwoInjectBean(TwoInjectBean twoInjectBean) { this.twoInjectBean = twoInjectBean; } }
如果我们以属性的方式自动装配,则可以像如下这样配置:
@Component public class SimpleBean { @Autowired private OneInjectBean oneInjectBean; @Autowired private TwoInjectBean twoInjectBean; }
在自动装配的使用上,
Spring
团队推荐:
- 对于自动装配应该在整个项目中一致使用才能达到最佳的效果。如果在普遍不使用自动装配的应用中,对某一两个
bean
使用自动装配可能会让人感到困惑。- 在大规模的应用中,更多的是使用默认配置(即
no
模式)。因为明确的指定依赖可以提供更好的控制性和清晰性,并且在某种程度上它记录了一个系统的整体结构。
另外,我们还可以通过将bean
的autowire-candidate
属性设置为false
来使它不会成为其他bean
依赖的自动装配候选者(但这并不意味着排除了bean
本身不能使用自动装配对其依赖进行配置,而是它不能是其他bean
自动装配的候选者)。其中,需要注意的是autowire-candidate
属性只能影响到基于类型的自动装配(即byType
或者constructor
),它并不会影响到基于名称的自动装配(即byName
),即使指定的bean
并没有成为自动装配候选者也会得到解析。与上述其它属性类似,对于非自动装配候选者的配置也存在XML
的方式和Java
方式,具体如下所示:
-
通过
XML
配置bean
为非自动装配候选者在
XML
配置中,要将bean
配置为非自动装配候选者,则需要在bean
标签中将autowire-candidate
属性设置为false
,即:<beans> <bean id="simpleBean" class="com.example.SimpleBean" autowire-candidate="false"/> </beans>
除此之外,对于
XML
配置我们还可以在顶级<beans/>
元素的default-autowire-candidates
属性上指定一个或多个匹配模式(多个模式之间用逗号分隔),使得它包含的所有的<bean>
只有在模式匹配的前提下才能成为自动装配的候选者。需要注意的是,<bean>
中autowire-candidate
属性(需显式设置true
或者false
)的优先级是比其父标签<beans/>
元素的default-autowire-candidates
属性要高的。<beans default-autowire-candidates="*Repository"> <bean id="simpleBean" class="com.example.SimpleBean"/> <bean id="simpleRepository" class="com.example.SimpleRepository"/> </beans>
此处的
default-autowire-candidates="*Repository"
表示只有名称以Repository
结尾的<bean>
(在父标签<beans>
中)才能成为自动装配的候选者。 -
通过
Java
配置bean
为非自动装配候选者在
Java
配置中,要将bean
配置为非自动装配候选者,则需要在@Bean
注解中将autowireCandidate
属性设置为false
,即:@Configuration public class MyConfig { @Bean(autowireCandidate = false) public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { return new SimpleBean(oneInjectBean, twoInjectBean); } }
关于自动装配的优/劣势:
优势:
- 自动装配可以显著减少属性或构造器参数配置的需要。
- 自动装配可以随着对象变化而更新配置。例如,如果需要向
bean
添加依赖,自动装配可以在无需修改配置的前提下完成。劣势:
- 自动装配中属性和构造器参数的配置会被显式依赖项所覆盖。
- 自动装配不适用于简单属性及其类型数组,例如
Primitive
(原始类型)、String
和Class
。- 自动装配精确性没有显式装配高,并且它会影响到
IoC
容器中对象关系的记录。- 自动装配使得我们无法在
IoC
容器文档生成工具中获得装配信息。- 自动装配可能会在多个
bean
与依赖项相匹配的时候抛出异常。如果依赖项是数组、集合或Map
等类型时可能并不会存在问题,但是对于期望单个值的依赖项则会因为没有得到唯一的bean
实例而抛出异常。而对于这种情况我们可以有以下几种选择:
- 放弃自动装配转而使用显式装配。
- 通过将
bean
的autowire-candidate
属性设置为false
来避免自动装配。- 通过将
bean
设置为primary
来指定某个bean
为primary
候选者。- 基于注解实现更细粒度的控制。
更多详情可阅读一下资料:
延迟初始化
在默认情况下,IoC
容器在初始化时会对所有单例bean
进行实例化。除此之外,对于一些特殊的需要我们也可以将单例bean
的实例化进行延迟,这样它就不会在容器初始化时被创建,而是在第一次向IoC
容器请求bean
时才进行实例化。
一般我们是推荐
bean
跟着容器初始化一起创建的,因为这可以在启动的时候立即发现配置或环境出现问题而引发的错误,而不是在运行过程中才发现问题。
同样的,在使用上我们可以使用XML
的方式和Java
的方式进行配置,即:
-
通过
XML
的方式配置延迟初始化在
XML
配置中,我们可以将<bean/>
标签上的lazy-init
属性设置true
或false
来控制是否延长初始化。<beans> <!-- 通过指定lazy-init属性为true即可让SimpleBean不会在容器启动时进行实例化 --> <bean id="simpleBean" class="com.example.SimpleBean" lazy-init="true"/> </beans>
另外,我们还可以通过在
<beans/>
元素上使用default-lazy-init
属性来让它所包含的<bean>
都实现延迟初始化。<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
-
通过
Java
的方式配置延迟初始化在
Java
配置中,我们可以通过@Lazy
注解来达到与lazy-init
同样的效果(即延迟初始化)。其中,@Lazy
存在以下几种不同的声明方式:通过将
@Lazy
注解声明在@Component
及其衍生类(@Controller
、@Service
或@Repository
)上以让它实现延迟初始化。@Lazy @Component public class SimpleBean { }
通过将
@Lazy
注解声明在@Bean
方法上以让它实现延迟初始化。@Configuration public class MyConfig { @Lazy @Bean public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { return new SimpleBean(oneInjectBean, twoInjectBean); } }
通过将
@Lazy
注解声明在@Configuration
类上以让它所包含的所有@Bean
方法都被延迟初始化。另外,我们也可以在@Configuration
中的@Bean
上加上@Lazy(value=false)
(显式设置为不延迟初始化)来覆盖类上指定的默认行为(延迟初始化)。@Lazy @Configuration public class MyConfig { @Bean public SimpleBean1 simpleBean1(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { return new SimpleBean1(oneInjectBean, twoInjectBean); } @Lazy(value=false) @Bean public SimpleBean2 simpleBean2(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { return new SimpleBean2(oneInjectBean, twoInjectBean); } }
通过将
@Lazy
注解声明在依赖的@Autowired
或@Inject
注解上以让依赖延迟注入。通过这种方式,Spring
会首先会对@Lazy
的依赖注入一个lazy-resolution
代理,在依赖被调用时才会去检索或创建依赖实例(这可能会由于依赖不存在而抛出异常)。@Component public class SimpleBean { @Lazy @Autowired private OneInjectBean oneInjectBean; @Lazy @Autowired private TwoInjectBean twoInjectBean; }
@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Lazy @Autowired public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Lazy @Autowired public void setOneInjectBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } @Lazy @Autowired public void setTwoInjectBean(TwoInjectBean twoInjectBean) { this.twoInjectBean = twoInjectBean; } }
除此之外,对于依赖的延迟注入除了可以使用
@Lazy
注解我们还可以使用org.springframework.beans.factory.ObjectFactory
、jakarta.inject.Provider
或org.springframework.beans.factory.ObjectProvider
来代替,即将它们作为注入点。@Component public class SimpleBean { @Autowired private ObjectProvider<OneInjectBean> oneInjectBean; @Autowired private ObjectProvider<TwoInjectBean> twoInjectBean; }
@Component public class SimpleBean { private ObjectProvider<OneInjectBean> oneInjectBean; private ObjectProvider<TwoInjectBean> twoInjectBean; @Autowired public SimpleBean(ObjectProvider<OneInjectBean> oneInjectBean, ObjectProvider<TwoInjectBean> twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
@Component public class SimpleBean { private ObjectProvider<OneInjectBean> oneInjectBean; private ObjectProvider<TwoInjectBean> twoInjectBean; @Autowired public void setOneInjectBean(ObjectProvider<OneInjectBean> oneInjectBean) { this.oneInjectBean = oneInjectBean; } @Autowired public void setTwoInjectBean(ObjectProvider<TwoInjectBean> twoInjectBean) { this.twoInjectBean = twoInjectBean; } }
需要注意,当延迟初始化
bean
是普通单例bean
(非延迟初始化)的依赖时,容器在启动期间还是会创建这个延迟初始化的bean
,这是因为单例bean
仅仅会在容器启动时被创建,之后它的依赖将无法再被注入进去了。对于这种情况,我们可以通过将依赖也声明为lazy
(例如,在依赖属性上加上注解@Lazy
)来解决。更多详情可阅读一下资料:
依赖显式化
对于存在依赖关系的bean
(使用了Java
(通过@Autowired
等注解)或XML
(通过ref
属性或标签等)的方式声明了依赖项),IoC
容器会在初始化时会根据它们的依赖关系(顺序)逐个进行实例化。然而,对于一些没那么直接的依赖关系(例如,数据库驱动程序的注册)通过这种方式则无法实现。因此,Spring
提供了depends-on
属性让我们可以显示地指定bean
的依赖关系,以此来强制它们的初始化顺序。
需要注意,通过这种方式来指定初始化顺序仅仅适用于单例
bean
。
同样的,在使用上我们可以使用XML
的方式和Java
的方式进行配置,即:
-
通过
XML
方式配置depneds-on
在
XML
配置中,我们可以在<bean/>
标签中将depends-on
属性设置为需要依赖的bean
名称来显式指定它们依赖关系的,以此强制它们的初始化顺序。<beans> <bean id="simpleBean1" class="com.example.SimpleBean1" depends-on="simpleBean2"/> <bean id="simpleBean2" class="com.example.SimpleBean2"/> </beans>
如果
bean
对多个bean
存在依赖,则可以通过分隔符(逗号、空格和分号都是有效的分隔符)来间隔多个bean
名称。<beans> <bean id="simpleBean1" class="com.example.SimpleBean1" depends-on="simpleBean2,simpleBean3"/> <bean id="simpleBean2" class="com.example.SimpleBean2"/> <bean id="simpleBean3" class="com.example.SimpleBean3"/> </beans>
-
通过
Java
方式配置depneds-on
在
Java
配置中,我们可以在@Component
及其衍生类(@Controller
、@Service
或@Repository
)中或者@Bean
方法上使用注解@DependsOn
来达到目的,其中对于@DependsOn
注解的用法与XML
中depends-on
属性的用法相同。@DependsOn(value={"simpleBean2","simpleBean3"}) @Component public class SimpleBean1 { } @Component public class SimpleBean2 { } @Component public class SimpleBean3 { }
@Configuration public class MyConfig { @DependsOn(value={"simpleBean2","simpleBean3"}) @Bean public SimpleBean1 simpleBean1() { return new SimpleBean1(); } @Bean public SimpleBean2 simpleBean2() { return new SimpleBean2(); } @Bean public SimpleBean3 simpleBean3() { return new SimpleBean3(); } }
需要注意,
depends-on
属性不但可以指定依赖间的初始化顺序,对于单例bean
它还会根据其初始化顺序间接指定其销毁的顺序。即,在给定bean
销毁前必须先销毁依赖它的所有bean
。更多详情可阅读一下资料:
常见问题
自引用
自Spring 4.3
,@Autowired
注解开始支持自引用(即,自己引用自己)。然而,实际上@Autowired
的自引用在Spring
中充当的是一种后备策略,即一般情况下@Autowired
的自引用并不会在依赖注入候选者的名单中,只有在其它候选者都不满足的情况下它才会作为一种后备被注入到依赖中。可选地,我们也可以使用其它解决方案来实现自引用,即可以使用@Resource
注解指定唯一bean
名称的方式来实现自引用。
除此之外,将以@Bean
方法声明的bean
注入到同一配置类中实际上也是一种自引用的场景。对于这种情况,我们可以将相应的@Bean
方法声明为@Lazy
或static
,目的是让@Bean
方法的生命周期与配置类本身进行分离,否则容器只会在兜底阶段才考虑这些bean
(而是选择其他配置类中相匹配的bean
作为主要候选者(如果存在))。
更多详情可阅读一下资料:
循环依赖
在IoC
容器启动或者第一次请求bean
时会触发相应bean
(默认情况下单例bean
会在容器启动时被创建)以及其依赖项、依赖项的依赖项(等等)的创建。在这过程中可能会触发bean
的循环依赖,例如:
@Component
public class Component1 {
private Component2 component2;
@Autowired
public Component1(Component2 component2){
this.component2 = component2;
}
}
@Component
public class Component2 {
private Component1 component1;
@Autowired
public Component2(Component1 component1) {
this.component1 = component1;
}
}
对于像上述这样使用构造器注入的情况,在发生循环依赖时IoC
会在运行时检测出来,并且抛出BeanCurrentlyInCreationException
异常。在发生循环依赖时抛出异常可能并不是我们想要的,所以Spring
也提供了其他的注入方式来解决循环依赖的问题(不但检测出循环依赖,而且也解决循环依赖),例如在以Setter
方式配置依赖注入时,若发生循环依赖,它会迫使其中一个bean
在完全初始化之前注入到另一个bean
中(经典的鸡和蛋场景),具体用法如下:
@Component
public class Component1 {
private Component2 component2;
@Autowired
public void setComponent2(Component2 component2){
this.component2 = component2;
}
}
@Component
public class Component2 {
private Component1 component1;
@Autowired
public void setComponent1(Component1 component1){
this.component1 = component1;
}
}
更多详情可阅读一下资料:
Bean
作用域
在Spring
中,IoC
容器中的每个bean
都会被指定一个作用域(默认是singleton
),通过这个作用域我们就可以指定每次生成bean
实例的存活时间。其中,Spring
(默认)提供了以下六种可选值(有四种只有在Web
应用中可以使用):
作用域 | 描述 |
---|---|
singleton |
(默认)将单个bean 的作用域限定为每个容器只有单个对象实例。 |
prototype |
将单个bean 的作用域限定为任意数量的对象实例。 |
request |
将单个bean 的作用域限定为单个HTTP 请求的生命周期。也就是说,每个HTTP 请求都有自己的bean 实例(仅在Web 应用的上下文中有效)。 |
session |
将单个bean 的作用域限定为HTTP 会话的生命周期(仅在Web 应用的上下文中有效)。 |
application |
将单个bean 的作用域限定为ServletContext 的生命周期(仅在Web 应用的上下文中有效)。 |
websocket |
将单个bean 的作用域限定为WebSocket 的生命周期(仅在Web 应用的上下文中有效)。 |
作用域的可选值
Singleton
当bean
的作用域为singleton
时,它仅会在容器中存在一个共享实例,并且所有与它ID
匹配的请求容器都会返回指定的bean
。换句话说,当您定义了一个作用域为singleton
的bean
,IoC
容器会在bean
实例首次创建后将它存储到单例bean
的缓存中,并且后续对它的所有请求和引用都会返回缓存中的实例。
下图展示了singleton
作用域的工作原理:
Spring
概念中的单例bean
与Gang of Four (GoF)
设计模式书中定义的单例模式有所不同。GoF
中的singleton
是对象作用域的硬编码,每个ClassLoader
只创建特定类的一个实例;而Spring
的singleton
作用域则是每个容器只创建特定类的一个实例,即每个IoC
容器仅会创建特定类(通过BeanDefinition
定义的类)的一个实例(在Spring
中,singleton
作用域是bean
的默认作用域)。更多详情可阅读一下资料:
Prototype
当bean
的作用域为prototype
(非单例)时,它会在每次对容器请求bean
时(通过bean
注入或者调用getBean()
)都会创建一个新的bean
实例。
通常,我们应该对所有有状态的
bean
使用prototype
作用域,而对无状态的bean
使用singleton
作用域。
下图说明了Spring
的prototype
作用域的工作原理:
与其他bean
作用域相比,Spring
并不会管理prototype
作用域bean
的完整生命周期。对于prototype
作用域的bean
,容器会在实例化、配置和组装后直接将其传递给客户端,而没有像单例模式那样进行缓存。也正因如此,容器并不会在prototype
作用域的bean
销毁时执行其所配置的销毁方法(bean
生命周期回调)。所以,在prototype
作用域的bean
销毁时我们必须显式地清理对象及释放它所持有的资源。
需要注意,当将
prototype
作用域的bean
作为依赖注入到singleton
作用域的bean
时,prototype
作用域的bean
仅仅会singleton
作用域的bean
初始化时进行创建和注入。之后如果再次请求prototype
作用域的bean
(新)实例时,容器是不会再次将它注入到singleton
作用域的bean
中的,这是因为对于singleton
作用域的bean
仅仅是会在容器实例化bean
时完成依赖项的解析和注入。而对于这种情况我们可以通过方法注入Method Injection
的方式或作用域代理ScopedProxyMode
的方式来实现重复注入。更多详情可阅读一下资料:
Web Scope
对于Request
, Session
, Application
和Websocket
等作用域只有在Web
应用时才能生效,如果我们在普通容器中使用这些作用域则抛出IllegalStateException
异常。
Request
当bean
的作用域为request
时,它会在每次HTTP
请求创建新的bean
实例。也就是说,对于request
作用域的bean
生命周期会被限制在一次HTTP
请求,在请求处理完成时request
作用域的bean
将会被销毁。我们可以根据需要改变实例的内部状态,因为通过其他HTTP
请求生成的bean
实例并不能看见这些改变。
更多详情可阅读一下资料:
Session
当bean
的作用域为session
时,它会在每次HTTP
会话创建新的bean
实例。也就是说,对于session
作用域的bean
生命周期会被限制在一次HTTP
会话,在HTTP
会话被销毁时session
作用域的bean
也将会被销毁。我们可以根据需要改变实例的内部状态,因为通过其他HTTP
会话生成的bean
实例并不能看见这些改变。
更多详情可阅读一下资料:
Application
当bean
的作用域为application
时,它会在整个Web
应用程序创建新的bean
实例。也就是说,对于application
作用域的bean
生命周期会被限制在ServletContext
,并作为常规ServletContext
属性存储。这有点类似于Spring
的单例bean
,但在两个重要方面有所不同:
Application
作用域的bean
是每个ServletContext
的单例,而不是每个ApplicationContext
(在任何给定的Web
应用程序中一个ServletContext
可能有多个ApplicationContext
)。Application
作用域的bean
是公开的,可作为ServletContext
属性被访问。
更多详情可阅读一下资料:
WebSocket
当bean
的作用域为WebSocket
时,它会与WebSocket
会话的生命周期相关联,适用于跨WebSocket
应用。
更多详情可阅读一下资料:
作用域的配置
对于bean
作用域,我们可以分别通过XML
的方式和Java
的方式进行配置.
-
通过
XML
方式配置在基于
XML
的配置中,我们可以使用<bean/>
标签的scope
属性来指定其作用域。<beans> <!-- 默认是singleton作用域 --> <bean id="simpleBean" class="com.example.SimpleBean"/> <!-- singleton作用域 --> <bean id="simpleBean" class="com.example.SimpleBean" scope="singleton"/> <!-- prototype作用域 --> <bean id="simpleBean" class="com.example.SimpleBean" scope="prototype"/> <!-- request作用域 --> <bean id="simpleBean" class="com.example.SimpleBean" scope="request"/> <!-- session作用域 --> <bean id="simpleBean" class="com.example.SimpleBean" scope="session"/> <!-- application作用域 --> <bean id="simpleBean" class="com.example.SimpleBean" scope="application"/> </beans>
-
通过
Java
方式配置在基于
Java
的配置中,我们可以在@Component
类或者@Bean
方法上使用@Scope
注解并设置其scopeName
属性来声明bean
的作用域。而对于WebScope
作用域,Spring
还通过元注解组合的方式新增了对应的注解@RequestScope
、@SessionScope
和@ApplicationScope
。// 默认为singleton @Scope @Component public class SimpleBean { }
@Scope(scopeName="singleton") @Component public class SimpleBean { }
@Scope(scopeName="prototype") @Component public class SimpleBean { }
// 等价于@Scope(scopeName="request") @RequestScope @Component public class SimpleBean { }
// 等价于@Scope(scopeName="session") @SessionScope @Component public class SimpleBean { }
// 等价于@Scope(scopeName="application") @ApplicationScope @Component public class SimpleBean { }
更多详情可阅读一下资料:
作用域的依赖
由于作用域的不同,IoC
容器中bean
存活的生命周期也有所差别。如果需要将生命周期较短的bean
注入到生命周期更长的bean
,直接的依赖注入可能会产生问题。对于这种情况,我们可以使用AOP
代理来代替不同作用域的bean
,即我们需要注入的是一个(与作用域对象具有相同接口的)代理对象,它会从对应的作用域中检索出真正的目标实例,并将方法的调用委托给它。
-
通过
XML
方式配置注入代理在
XML
配置中,我们可以在<bean>
中加入子标签<aop:scoped proxy/>
以表示对该bean
生成代理对象。<beans> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="simpleBean" class="com.example.SimpleBean"> <property name="oneInjectBean" ref="oneInjectBean"/> <property name="twoInjectBean" ref="twoInjectBean"/> </bean> <!-- a prototype-scoped bean exposed as a proxy --> <bean id="oneInjectBean" class="com.example.OneInjectBean" scope="prototype"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a prototype-scoped bean exposed as a proxy --> <bean id="twoInjectBean" class="com.example.TwoInjectBean" scope="prototype"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> </beans>
- 在
singleton bean
上使用<aop:scoped proxy/>
,对bean
引用使用的是一个可序列化的代理对象,因此可以通过反序列化来重新获取实例。 - 在
prototype bean
上使用<aop:scoped proxy/>
,对注入代理每个方法的调用都会导致bean
实例重新创建,然后再将调用转发到该实例中。
默认情况下,
IoC
容器会使用CGLIB
为标记有<aop:scoped proxy/>
的bean
创建代理对象。而如果将<aop:scoped proxy/>
元素的proxy-target-class
属性设置false
,IoC
容器则会通过JDK
动态代理(基于JDK
接口的标准代理)来为bean
生成代理对象。- 根据
JDK
动态代理的定义,在使用时我们不需要添加额外的库来生成代理对象,但是需要为bean
实现至少一个接口。另外,需要注意对这种bean
依赖的注入必须通过其接口之一来引用。 - 根据
CGLIB
动态代理的定义,由于这种代理只会截获public
方法的调用,所以我们不能在这种代理上调用非public
方法,它们是不会被委托给目标实例的。
<beans> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="simpleBean" class="com.example.SimpleBean"> <property name="oneInjectBean" ref="oneInjectBean"/> <property name="twoInjectBean" ref="twoInjectBean"/> </bean> <!-- a prototype-scoped bean exposed as a proxy --> <bean id="oneInjectBean" class="com.example.DefaultOneInjectBean" scope="prototype"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy proxy-target-class="false"/> </bean> <!-- a prototype-scoped bean exposed as a proxy --> <bean id="twoInjectBean" class="com.example.DefaultTwoInjectBean" scope="prototype"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy proxy-target-class="false"/> </bean> </beans>
需要注意,如果将
<aop:scoped proxy/>
放在FactoryBean
实现的<bean>
时,作用域是FactoryBean
本身,而不是从FactoryBean#getObject()
返回的对象。另外,如果需要设置大量的代理对象,我们可以在
component-scan
上使用scoped-proxy
属性来指定。其中,可选值为no
、interfaces
和targetClass
。<beans> <context:component-scan base-package="org.example" scoped-proxy="targetClass"/> </beans>
- 在
-
通过
Java
方式配置注入代理在
Java
配置中,我们可以在@Scope
注解上通过指定proxyMode()
属性来设置其作用域代理,它的可选值可在ScopedProxyMode
枚举中找到,默认为不创建代理(即ScopedProxyMode.DEFAULT
或者ScopedProxyMode.NO
)。ScopedProxyMode
描述 DEFAULT
默认,等价于 ScopedProxyMode.NO
。可通过在component-scan
上设置ScopedProxyMode
属性进行变更。NO
此类型表示不创建代理。 INTERFACES
此类型表示通过 JDK
动态代理创建代理类。TARGET_CLASS
此类型表示通过 CGLIB
动态代理创建代理类。@Scope(scopeName="singleton") @Component public class SimpleBean1 { @Autowired private SimpleBean2 simpleBean2; } @Scope(scopeName="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS) @Component public class SimpleBean2 { }
如果需要设置大量的代理对象,我们可以在
@ComponentScan
注解上使用scopedProxy
属性来指定。其中,可选值为no
、interfaces
和targetClass
。@Configuration @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.TARGET_CLASS) public class AppConfig { }
另外,除了通过代理类的方式将生命周期较短的
bean
注入到生命周期更长的bean
外,我们还可以通过将注入点(即构造参数、Setter
参数或自动装配字段)声明为ObjectFactory<MyTargetBean>
来实现,这样每次调用ObjectFactory#getObject()
来获取依赖对象就会按需检索依赖实例(无需保留或单独存储实例)。ObjectFactory<MyTargetBean>
作为一个扩展变量,你还可以声明ObjectProvider<MyTargetBean>
,它实现了ObjectFactory<MyTargetBean>
并提供了几个额外的访问方法,比如getIfAvailable()
和getIfUnique()
。另外,对于JSR-330
规范它也有相同作用的变体Provider
,类似的可以通过Provider<MyTargetBean>
的方法Provider#get()
来检索依赖实例。
更多详情可阅读一下资料:
Bean
生命周期
Lifecycle
接口
Initialization
和Destruction
当bean
实现了InitializingBean
和DisposableBean
接口时,IoC
容器就会在bean
初始化和销毁时分别调用InitializingBean#afterPropertiesSet
方法(hook
)和DisposableBean#destroy
方法(hook
)让bean
能在特定的生命周期上执行特定的操作。
@Component
public class SimpleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
IoC
容器会在设置完所有必要的属性后调用org.springframework.beans.factory.InitializingBean
接口类(如有)来指定初始化工作,其中InitializingBean
接口只有一个方法,即:void afterPropertiesSet() throws Exception;
@Component
public class SimpleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
IoC
容器会在bean
被销毁时调用org.springframework.beans.factory.DisposableBean
接口类(如有)来指定销毁工作,其中DisposableBean
接口只有一个方法,即:void destroy() throws Exception;
实际上,除了实现相应的接口方法外Spring
更推荐我们使用JSR-250
标准中@PostConstruct
注解(初始化)和@PreDestroy
注解(注销)来标记bean
的初始化方法(hook
)和销毁方法(hook
),因为通过注解的方式可以让bean
无需耦合Spring
指定的接口(如果不想使用JSR-250
注解但是又不想耦合Spring
指定的接口,可以考虑在bean
配置中指定init-method
属性和destroy-method
属性)。具体用法如下所示:
-
通过
XML
配置的方式在
XML
中,我们可以通过<bean/>
标签的init-method
属性和destroy-method
属性分别指定初始化方法和销毁方法。<bean id="simpleBean" class="com.example.SimpleBean" init-method="init" destroy-method="cleanup"/>
public class SimpleBean { public void init() { // do some initialization work } public void cleanup() { // do some destruction work (like releasing pooled connections) } }
-
通过
Java
配置的方式在
Java
中,我们可以通过@Bean
注解的initMethod
属性和destroyMethod
属性分别指定初始化方法和销毁方法。@Configuration public class MyConfig { @Bean(initMethod="init", destroyMethod="cleanup") public SimpleBean simpleBean() { return new SimpleBean(); } }
public class SimpleBean { public void init() { // do some initialization work } public void cleanup() { // do some destruction work (like releasing pooled connections) } }
而在
Spring 2.5
后,我们可以通过@PostConstruct
注解和@PreDestroy
注解分别指定初始化方法和销毁方法。@Configuration public class MyConfig { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } }
// 直接用@Component也可以 public class SimpleBean { @PostConstruct public void init() { // do some initialization work } @PreDestroy public void cleanup() { // do some destruction work (like releasing pooled connections) } }
实际上,Spring
是使用BeaPostProcessor
来处理任何生命周期相关的接口的,如果需要自定义更多特性或其他Spring
没有提供的生命周期行为,我们可以为此实现一个专用的BeanPostProcessor
。另外,需要注意Spring
会在bean
完成所有依赖注入后立即调用配置的初始化方法,这意味着在bean
执行初始化方法时AOP
拦截器尚未应用于bean
。
Spring
建议我们即使不使用InitializingBean
和DisposableBean
等接口来执行初始化方法和销毁方法,对于生命周期的接口方法命名也应该是整个项目统一的(存在一个标准,例如init()
、initialize()
、dispose()
等名称编写方法),这样我们就可以在需要时低成本地使用XML
中<beans>
的default-init-method
属性和default-destroy-method
属性来指定它当中所有<bean>
的初始化方法和销毁方法。<beans default-init-method="init" default-destroy-method="cleanup"> <bean id="simpleBean" class="com.example.SimpleBean"/> </beans>
当然,我们也可以在
bean
中设置init-method
和destroy-method
属性来覆盖其父级的默认方法(default-init-method
和default-destroy-method
)。
总的来说,我们可以通过三种方式来控制bean
生命周期行为(从Spring2.5
):
InitializingBean
和DisposableBean
接口init()
和destroy()
方法(自定义)@PostConstruct
和@PreDestroy
注解.
对于上述三种方式我们可以组合使用。如果一个bean
配置了多种生命周期机制,并且每种机制都配置了不同的方法名,那么每个配置的方法都会按照以下顺序执行:
- 对同一个
bean
的多种初始化方法执行顺序:@PostConstruct
注解方法InitializingBean#afterPropertiesSet()
方法init()
方法(自定义配置)
- 对同一个
bean
的多种销毁方法执行顺序:@PreDestroy
注解方法DisposableBean#destroy()
方法destroy()
方法(自定义配置)
其中,如果对不同的方式都配置了相同的方法名称,则该方法将运行一次。
更多详情可阅读一下资料:
Startup
和Shutdown
除了上述提及在特定生命周期触发的钩子方法外,Spring
还提供了定义了生命周期中用于启动和停止的Lifecycle
接口(可用于启动和停止某些进程)。
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
在Spring
中,任何实现了Lifecycle
接口的(被Spring
管理的)对象都会在ApplicationContext
(IoC
容器)收到启动和停止信号时被级联触发。在实现上,它是通过LifecycleProcessor
来实现这一点:
/**
* LifecycleProcessor继承了Lifecycle接口,并且添加了两种额外的方法,用于响应刷新和关闭的上下文。
*/
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
需要注意
Spring
并没有保证在bean
销毁前一定会有停止通知发出,一般来说Lifecycle
实现会在接收到销毁方法(destruction
)的回调前先收到停止的通知(Lifecycle#stop
),但是对于在上下文生命周期内的热更新或者在停止刷新的间隔内它们只会收到销毁方法(destruction
)的回调。
虽然Lifecycle
接口定义了用于启动和停止的方法,但是这并不意味着它会在上下文刷新时自动启动。如果需要更细粒度地控制自动启动和特定bean
的优雅停止,可以考虑继承SmartLifecycle
接口。
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
SmartLifecycle
除了扩展Lifecycle
外,还增加了Phased
的扩展。对于两个存在依赖关系的对象它们启动和停止的顺序是十分重要的,比如依赖方在其依赖之后开始,并在其依赖之前停止。但是,在开发过程中我们并不一定能知道两个具体对象的直接依赖关系,而是只知道某种类型的对象应该在另一种类型的对象之前开始,对于这种情况我们就需要用到Phased
的扩展了:public interface Phased { int getPhase(); }
Phased
接口主要用于指定对象的启动相位,即在启动时相位最低的对象首先启动,在停止时则按照相反的顺序进行停止。因此对于getPhase()
方法返回Integer.MIN_VALUE
的对象将是最先启动和最后停止的对象;在频谱的另一端,Integer.MAX_VALUE
的phase
值则表示该对象应该最后启动并首先停止。另外,默认情况下任何未实现SmartLifecycle
的“正常”Lifecycle
对象的phase
都是0
,所以任何负phase
值表示对象应该在这些标准组件之前开始(并在他们之后停止),反之亦然。
SmartLifecycle
在Lifecycle
的基础上增加了可接收Runnable
参数的stop
方法,默认会在执行完Lifecycle#stop
方法后立即调用Runnable#run
方法(同一调用线程)。在加入这个方法后,我们可以很轻易的实现SmartLifecycle
组件的异步关闭(通过回调的方式实现)以支持功能的需要,例如LifecycleProcessor
的默认实现DefaultLifecycleProcessor
会在执行组件停止时对每个phase
对象组的执行时间(含Runnable
回调的执行)设置有超时时间,对于这种情况我们就可以启动异步关闭了(如有需要)。
默认情况下每个
phase
组的超时时间为30
秒。我们也可以通过在上下文中定义一个名为lifecycleProcessor
的bean
来覆盖默认的生命周期处理器实例。而如果要修改超时时间,添加以下配置即可:<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <!-- timeout value in milliseconds --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean>
另外,LifecycleProcessor
接口还定义了刷新回调方法onRefresh()
(hook
)和关闭回调方法onClose()
(hook
),其中前者会在上下文刷新时(在所有对象都被实例化和初始化之后)被触发,默认情况下处理器会检查每个SmartLifecycle
的isAutoStartup()
方法,当它返回true
时当前实例就会在该执行点被自动启动,而不是等待上下文的start
方法被显式调用来启动(与上下文刷新不同,在标准的上下文实现中start
是不会自动发生的);而后者则会在上下文关闭时被触发,效果与显式调用stop
方法类似,只不过时机是发生在上下文被关闭。
对于在非
WEB
应用环境下使用IoC
容器,如果想要对每个单例bean
优雅地执行关闭,则需要配置和实现相对应的destroy
方法(释放资源),并且需要想向JVM
注册一个shutdown
的hook
方法,即调用ConfigurableApplicationContext
接口的registerShutdownHook()
方法(在基于Web
应用环境的ApplicationContext
实现已经存在相关代码,所以无需额外配置)。import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Boot { public static void main(final String[] args) throws Exception { ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); // add a shutdown hook for the above context... ctx.registerShutdownHook(); // app runs here... // main method exits, hook is called prior to the app shutting down... } }
更多详情可阅读一下资料:
Aware
接口
Spring
提供了各种可感知的回调接口,让bean
可以向容器表明它们需要某种基础设施依赖。下面我们来看看几个比较常见的Aware
接口:
-
ApplicationContextAware
当
ApplicationContext
(IoC
容器)创建一个org.springframework.context.ApplicationContextAware
对象实例时,该实例就会通过ApplicationContextAware#setApplicationContext
提供这个ApplicationContext
的实例引用给我们。public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
在获取到
ApplicationContext
后我们就可以通过它(以编程的方式)来创建和获取更多的bean
了。虽然有时候这种能力很有用,但是我们更应该避免以这种方式来操作bean
,因为这会导致业务代码耦合到Spring
并且它也不遵循IoC
风格。除此之外,我们还可以通过自动装配的方式来获取
ApplicationContext
。即,通过构造参数、Setter
方法参数或字段属性等自动装配的方式来对ApplicationContext
进行注入。 -
BeanNameAware
当
ApplicationContext
(IoC
容器)创建一个org.springframework.beans.factory.BeanNameAware
对象实例时,该实例就会通过BeanNameAware#setBeanName
提供所关联bean
实例的名称给我们。public interface BeanNameAware { void setBeanName(String name) throws BeansException; }
需要注意,
BeanNameAware#setBeanName
方法会在普通bean
属性填充后初始化方法回调(InitializingBean#afterPropertiesSet
方法或者指定init-method
方法)前被调用。 -
更多
Aware
接口除了
ApplicationContextAware
和BeanNameAware
外,Spring
还提供了广泛的Aware
回调接口:Name Injected Dependency ApplicationContextAware Declaring ApplicationContext. ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext. BeanClassLoaderAware Class loader used to load the bean classes. BeanFactoryAware Declaring BeanFactory. BeanNameAware Name of the declaring bean. LoadTimeWeaverAware Defined weaver for processing class definition at load time. MessageSourceAware Configured strategy for resolving messages (with support for parameterization and internationalization). NotificationPublisherAware Spring JMX notification publisher. ResourceLoaderAware Configured loader for low-level access to resources. ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext. ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext.
更多详情可阅读一下资料:
Bean
加载
在完成对bean
的配置后,我们就可以使用Spring
提供的方式对bean
进行加载了,例如,通过将配置文件或配置类设置到容器ApplicationContext
中进行加载;或者也可以通过@ComponentScan
注解扫描指定路径上的配置文件进行加载,又或者通过@Import
注解直接导入配置类的方式进行加载。
通过ApplicationContext
加载
对于传统Java
编程的方式,我们可以将配置文件或者配置类设置到ApplicationContext
中来加载bean
,其中根据不同的文件类型可分为FileSystemXmlApplicationContext
、ClassPathXmlApplicationContext
和AnnotationConfigApplicationContext
。
-
通过
XML
配置的方式对于
XML
配置的方式,我们可以通过将文件系统路径和classpath
路径分别配置到FileSystemXmlApplicationContext
和ClassPathXmlApplicationContext
中进行bean
的加载。ApplicationContext classPathContext = new ClassPathXmlApplicationContext("beans.xml"); MyService service = classPathContext.getBean("myService", MyService.class);
ApplicationContext fileSystemContext = new FileSystemXmlApplicationContext("resources/beans.xml"); MyService service = fileSystemContext.getBean("myService", MyService.class);
-
通过
Java
配置的方式对于
Java
配置的方式,我们可以将@Configuration
、@Component
等配置类设置到AnnotationConfigApplicationContext
中进行bean
的加载。ApplicationContext annotationConfigContext = new AnnotationConfigApplicationContext(MyConfig.class); MyService service = annotationConfigContext.getBean("myService", MyService.class);
除此之外,我们也可以在
ApplicationContext
中调用getBeanFactory()
获取BeanFactory
(默认实现为DefaultListableBeanFactory
),然后调用BeanFactory
(默认实现为DefaultListableBeanFactory
)的registerSingleton()
和registerBeanDefinition()
方法来注册容器外的对象以创建对应的bean
实例。
更多详情可阅读一下资料:
通过@ComponentScan
加载
除了使用传统Java
编程的方式,Spring
还提供了自动检测的方式来加载bean
,即通过使用@ComponentScan
注解类完成对指定范围内bean
的自动检测(扫描),并最终将对应BeanDefinition
实例注册到容器中(例如ApplicationContext
)。
当使用
@ComponentScan
进行自动检测时AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
都会被隐式包含在内。如果是通过它的XML
方式进行配置(使用<context:component-scan>
),则可以通过将annotation-config
属性设置为false
来禁用AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
的注册。
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
默认情况下,@Component
、@Repository
、@Service
、@Controller
、@Configuration
注解及其衍生注解都会被检测到,并且我们还可以通过在@ComponentScan
注解上设置includeFilters
或excludeFilters
属性配置自定义过滤器修改和扩展此行为,其中每个过滤器都需要配置type
和expression
属性。下表描述了过滤的选项:
Filter Type |
Example Expression |
Description |
---|---|---|
annotation (default) |
org.example.SomeAnnotation |
在目标组件的类级别上存在相应的注解。 |
assignable |
org.example.SomeClass |
目标组件可分配给所指定的类(或接口)。 |
aspectj |
org.example..*Service+ |
目标组件相匹配的AspectJ 类型表达式。 |
regex |
org\.example\.Default.* |
目标组件相匹配的正则表达式(类名)。 |
custom |
org.example.MyTypeFilter |
org.springframework.core.type.TypeFilter 接口的自定义实现。 |
如果需要禁用默认的扫描过滤策略,我们可以通过在注解上设置useDefaultFilters=false
或在<component-scan/>
标签上设置use-default-filters
为'false'
来禁用(默认过滤器)。这可以有效地禁用了对@Component
、@Repository
、@Service
、@Controller
、@RestController
或@Configuration
注解及其衍生注解的自动检测。
更多详情可阅读一下资料:
通过@Import
加载
另外,我们还可以通过@Import
注解来添加额外的配置类(例如,@Configuration
类)。
本章节主要讨论如何使用
@Import
注解来添加额外的配置类,而对于使用<import>
标签(若以XML
的方式配置)或者使用@ImportResource
注解(若以Java
的方式配置)来引用其他的XML
配置文件在这里就不展开讨论了,有兴趣的读者可进一步阅读相关资料进行了解。
/**
* Indicates one or more <em>component classes</em> to import — typically
* {@link Configuration @Configuration} classes.
*
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
* Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
* classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
*
* <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
* accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
* injection. Either the bean itself can be autowired, or the configuration class instance
* declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
* navigation between {@code @Configuration} class methods.
*
* <p>May be declared at the class level or as a meta-annotation.
*
* <p>If XML or other non-{@code @Configuration} bean definition resources need to be
* imported, use the {@link ImportResource @ImportResource} annotation instead.
*
* @see Configuration
* @see ImportSelector
* @see ImportBeanDefinitionRegistrar
* @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
通过@Import
注解我们不但可以加载一些典型的配置类(例如@Configuration
和@Component
注解类及其衍射注解的类),而且还可以还可以加载ImportSelector
和ImportBeanDefinitionRegistrar
类进行更灵活的配置。
我们可以将
@Import
作为一个元注解(meta-annotation
)衍射出更多灵活的用户,典型的就是添加相应的@EnableXxxx
注解来使相应的第三方组件生效。而对于@Import
中指定ImportSelector
和ImportBeanDefinitionRegistrar
则更是众多第三方框架整个Spring
的关键所在。
-
常规配置类
对于常规配置类(例如
@Configuration
和@Component
注解类及其衍射注解的类)的导入会将配置类本身及其所包含的bean
配置注册和加载到IoC
容器中。@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }
-
ImportSelector
对于
ImportSelector
的导入则可以更灵活地进行bean
的注册和加载。/** * Interface to be implemented by types that determine which @{@link Configuration} * class(es) should be imported based on a given selection criteria, usually one or * more annotation attributes. * * <p>An {@link ImportSelector} may implement any of the following * {@link org.springframework.beans.factory.Aware Aware} interfaces, * and their respective methods will be called prior to {@link #selectImports}: * <ul> * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li> * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li> * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li> * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li> * </ul> * * <p>Alternatively, the class may provide a single constructor with one or more of * the following supported parameter types: * <ul> * <li>{@link org.springframework.core.env.Environment Environment}</li> * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li> * <li>{@link java.lang.ClassLoader ClassLoader}</li> * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li> * </ul> * * <p>{@code ImportSelector} implementations are usually processed in the same way * as regular {@code @Import} annotations, however, it is also possible to defer * selection of imports until all {@code @Configuration} classes have been processed * (see {@link DeferredImportSelector} for details). * * @author Chris Beams * @author Juergen Hoeller * @since 3.1 * @see DeferredImportSelector * @see Import * @see ImportBeanDefinitionRegistrar * @see Configuration */ public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. * @return the class names, or an empty array if none */ String[] selectImports(AnnotationMetadata importingClassMetadata); /** * Return a predicate for excluding classes from the import candidates, to be * transitively applied to all classes found through this selector's imports. * <p>If this predicate returns {@code true} for a given fully-qualified * class name, said class will not be considered as an imported configuration * class, bypassing class file loading as well as metadata introspection. * @return the filter predicate for fully-qualified candidate class names * of transitively imported configuration classes, or {@code null} if none * @since 5.2.4 */ @Nullable default Predicate<String> getExclusionFilter() { return null; } }
在
ImportSelector
的实现类中,我们需要在selectImports
方法中返回需要加载的@Configuration
类名称(通过Class.getName()
获取),这样Spring
容器会从selectImports
方法的返回值获取需要加载的@Configuration
类及其所包含的bean
。需要注意,IoC
容器在获取的同时会通过getExclusionFilter()
方法进一步执行过滤。除此之外,我们还可以通过ImportSelector
的衍生类DeferredImportSelector
来延迟加载@Configuration
类。DeferredImportSelector
会在其他所有@Configuration
类加载后再进行加载,而不同DeferredImportSelector
之间则是通过Ordered
接口或@Order
注解来指定其执行顺序的。对于
selectImports
方法的AnnotationMetadata
参数则表示@Import
注解所修饰类的元信息。 -
ImportBeanDefinitionRegistrar
与
ImportSelector
相似,将ImportBeanDefinitionRegistrar
传入@Import
同样可以对额外的bean
进行注册和加载。与之相比,ImportBeanDefinitionRegistrar
更接近底层,是直接通过构建BeanDefinition
注册到容器中的。ImportBeanDefinitionRegistrar
除了可以配置到@Import
外,还能配置到ImportSelector#selectImports
方法中。/** * Interface to be implemented by types that register additional bean definitions when * processing @{@link Configuration} classes. Useful when operating at the bean definition * level (as opposed to {@code @Bean} method/instance level) is desired or necessary. * * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type * may be provided to the @{@link Import} annotation (or may also be returned from an * {@code ImportSelector}). * * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #registerBeanDefinitions}: * <ul> * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li> * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware} * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware} * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware} * </ul> * * <p>Alternatively, the class may provide a single constructor with one or more of * the following supported parameter types: * <ul> * <li>{@link org.springframework.core.env.Environment Environment}</li> * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li> * <li>{@link java.lang.ClassLoader ClassLoader}</li> * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li> * </ul> * * <p>See implementations and associated unit tests for usage examples. * * @author Chris Beams * @author Juergen Hoeller * @since 3.1 * @see Import * @see ImportSelector * @see Configuration */ public interface ImportBeanDefinitionRegistrar { /** * Register bean definitions as necessary based on the given annotation metadata of * the importing {@code @Configuration} class. * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * <p>The default implementation delegates to * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry * @param importBeanNameGenerator the bean name generator strategy for imported beans: * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator} * has been set. In the latter case, the passed-in strategy will be the same used for * component scanning in the containing application context (otherwise, the default * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}). * @since 5.2 * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR * @see ConfigurationClassPostProcessor#setBeanNameGenerator */ default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry); } /** * Register bean definitions as necessary based on the given annotation metadata of * the importing {@code @Configuration} class. * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * <p>The default implementation is empty. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry */ default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
在
ImportBeanDefinitionRegistrar
中,我们可以在registerBeanDefinitions
方法上构建相应的BeanDefinition
,将它注册到BeanDefinitionRegistry
,并最终加载到IoC
容器。不过需要注意的是,由于@Configuration
相关生命周期的限制BeanDefinitionRegistryPostProcessor
是不可以在这里被注册的。对于
registerBeanDefinitions
方法的AnnotationMetadata
参数则表示@Import
注解所修饰的类的元信息。
更多详情可阅读一下资料:
Bean
扩展
Bean
的继承
在bean
的定义中会包含很多配置的信息,其中包括构造参数、属性值和特定于容器的信息,例如初始化方法、静态工厂方法名称等,这很有可能会造成项目存在大量重复bean
定义。为了避免这种情况,Spring
提供了bean
的继承,即我们可以通过继承的方式从父定义中继承bean
中的配置信息,而在子定义中则按照需要对某些值进行新增或覆盖。
一般来说,在IoC
容器中bean
会被定义为RootBeanDefinition
,而继承父定义的bean
则被定义为ChildBeanDefinition
。但这也不是绝对的,如果我们直接通过ApplicationContext
以编程的方式将bean
注册为ChildBeanDefinition
也是可以的。只不过我们更普遍地会在XML
的bean
定义中使用parent
来设置其父定义,这同时也会让当前bean
被注册为ChildBeanDefinition
(子定义)。
<bean id="simpleBean" class="com.example.SimpleBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="childSimpleBean" class="com.example.SimpleBean" parent="simpleBean" >
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
通过这种方式,子定义会从父定义中继承相对应的属性和方法,包括作用域、初始化方法、销毁方法或者静态工厂方法等(如有指定都可覆盖父定义);而剩余的其他配置总会以子定义为准(忽略父定义),包括depends on
、autowire mode
、dependency check
、singleton
和lazy init
。另外,较为特殊的class
属性也能被继承,如果子定义中没有指定class
属性是可以从父定义中继承来使用的;而如果子定义对父定义的class
属性进行覆盖则必须与父定义兼容,即必须接受父类的属性。需要注意,如果父定义并未指定其class
属性,则需要将它的abstract
属性设置为true
。
<bean id="simpleBean" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="childSimpleBean" class="com.example.SimpleBean" parent="simpleBean" >
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
对于被标记为abstract
的bean
仅仅只能作为子定义的模版而不能被实例化(如果强行获取会报错)。也正因如此,IoC
容器在执行preInstantiateSingletons()
方法时会忽略被标记为abstract
的bean
(默认情况下,ApplicationContext
会预实例化所有单例bean
。如果我们只想将某个bean
作为子定义的模版(指定了class
属性),则需要确保将其abstract
属性设置为true
,否则ApplicationContext
将试图预实例化它)。
需要注意,与
XML
配置不同的是在Java
配置中并没有bean
定义继承的概念(类级别的继承层次结构与此无关)。更多详情可阅读一下资料:
Bean
的注解
@Autowired
对于@Autowired
注解,我们可以将它标记在构造器、字段或配置方法(含Setter
方法)上,这样Spring
就会根据依赖注入的机制完成自动装配了。
-
构造器的自动装配
如果给定的
bean
只有一个构造器被标注了@Autowired
且required
属性为true
(required
属性默认为true
),则表示在实例化bean
时会通过此构造器进行自动装配。需要注意,对于required
属性为true
的@Autowired
只能被标注在一个构造器上。@Component public class SimpleBean { private final OneInjectBean oneInjectBean; private final TwoInjectBean twoInjectBean; @Autowired public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
如果给定的
bean
存在多个构造器被标注了required
属性为false
的@Autowired
(即有多个候选者),则在实例化bean
时会选择能满足匹配最多依赖的构造器进行自动装配;如果标注的多个构造器都没有满足时(没有候选者满足匹配),则会选择primary
/default
进行自动装配(如果存在)。@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; // 如果其他都不匹配时则会选择此构造函数,同时此处的@Autowired(required=false)并不是必须的 @Autowired(required=false) // 非必须 public SimpleBean() { } @Autowired(required=false) public SimpleBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } // 预先被选择作自动装配 @Autowired(required=false) public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
另外,从
Spring Framework 4.3
开始,如果给定的bean
只定义了一个构造器,即使没有添加@Autowired
注解该构造器也会被用来执行自动装配。而如果存在有多个构造器可用且没有primary/default
构造器,则必须至少在一个构造器上标注@Autowired
注解。@Component public class SimpleBean { private final OneInjectBean oneInjectBean; private final TwoInjectBean twoInjectBean; public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
需要注意:
- 对于标注了
@Autowired
注解的构造器并不必须是public
的。 - 对于将
@Autowired
注解标注在构造器上时,它的required
属性是针对所有参数的。
- 对于标注了
-
字段的自动装配
如果在字段上标注
@Autowired
注解,则它会在构造器执行(注入)完成后在任何配置方法被调用(注入)前对其进行自动装配。@Component public class SimpleBean { @Autowired private final OneInjectBean oneInjectBean; @Autowired private final TwoInjectBean twoInjectBean; }
此处需要注意,对于标注了
@Autowired
注解的字段并不必须是public
的。 -
方法的自动装配
如果在配置方法(
Setter
方法是配置方法中的一个特例)上标注@Autowired
注解,则方法上所有参数都将会被自动装配。@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Autowired public void injectOneInjectBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } // Setter方法是配置方法注入的特例 @Autowired public void setTwoInjectBean(TwoInjectBean twoInjectBean) { this.twoInjectBean = twoInjectBean; } }
需要注意:
- 对于标注了
@Autowired
注解的配置方法并不必须是public
的。 - 对于将
@Autowired
注解标注在配置方法上时,它的required
属性是针对所有参数的。
- 对于标注了
根据上文所述,如果我们要将@Autowired
注解标注在方法(含构造方法和配置方法)上时,它的required
属性是针对所有参数的。而如果我们需要对部分方法参数忽略required
属性的语义,那么我们可以通过将参数声明为java.util.Optional
(JDK8
特性)或者在参数上标注JSR-305
注解@Nullable
(Spring Framework 5.0
特性)。
@Component
public class SimpleBean {
private final Optional<OneInjectBean> oneInjectBean;
private final Optional<TwoInjectBean> twoInjectBean;
@Autowired
public SimpleBean(Optional<OneInjectBean> oneInjectBean, Optional<TwoInjectBean> twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
@Component
public class SimpleBean {
private final OneInjectBean oneInjectBean;
private final TwoInjectBean twoInjectBean;
@Autowired
public SimpleBean(@Nullable OneInjectBean oneInjectBean, @Nullable TwoInjectBean twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
对于
@Autowired
注解,我们可以通过它的required
属性来标注依赖是否必须注入,默认情况下对于所有标注了@Autowired
(包含指定required
为true
)的方法和字段都视为必须注入。而对于非必需的依赖我们可以通过将required
设置为false
让容器跳过不满足的注入点,否则注入可能会因运行时“未找到类型匹配”错误而失败。
另外,对于数组(Array
)、集合(Collection
)或哈希表(Map
)等依赖的自动装配IoC
容器会将所有与之类型相匹配的bean
进行注入。其中,所声明Map
的键(KEY
)必须是字符串(用于表示bean
名称,即其键(KEY
)即为bean
名称);而所声明Collection
会按照Ordered
接口或@Order
注解(标准@Priority
注解也可以)所指定的顺序值进行排序(如果存在),如果不存在Ordered
接口或@Order
注解则会按照注册到容器的顺序进行排序。
- 对于数组(
Array
)、集合(Collection
)或哈希表(Map
)等依赖的自动装配必须至少匹配一个bean
,否则将会启动发生错误(默认)。- 对于标准
javax.annotation.Priority
注解是不能声明在@Bean
方法上的,如果需要类似的功能可以使用@Order
注解或者@Primary
注解来代替。@Order
注解仅仅可能会影响到依赖注入的优先级,而不会影响bean
的启动顺序(启动顺序可由依赖关系或@DependsOn
来决定)。
@Component
public class SimpleBean {
@Autowired
private InjectBean[] injectBeanArray;
@Autowired
private Collection<InjectBean> injectBeanCollection;
@Autowired
private Map<String,InjectBean> injectBeanMap;
}
需要注意,
@Autowired
、@Inject
、@Value
和@Resource
注解是通过BeanPostProcessor
来实现的,所以这意味着我们不能在BeanPostProcessor
或BeanFactoryPostProcessor
(如果有)中应用这些注解来进行依赖注入。更多资料可阅读:
@Primary
对于@Primary
注解,主要用于标注某个指定bean
具有最高的依赖优先级。在自动装配时存在多个bean
候选者匹配到单值依赖项而引发错误时,我们可以通过@Primary
注解将某个候选者(bean
)标注为primary
(最高优先级),使得IoC
容器可以选出唯一的候选者而避免错误的发生。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
基于上述配置,下面会将firstMovieCatalog
自动装配到MovieRecommender
:
@Component
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
更多资料可阅读:
@Qualifier
对于@Qualifier
注解,主要用于通过指定bean
限定符的方式缩小依赖(类型)匹配的范围。
与
@Primary
注解相比,@Qualifier
注解对于bean
的选择进行了更多的控制。
@Configuration
public class MovieConfiguration {
@Bean
@Qualifier("firstMovieCatalog")
public MovieCatalog firstMovieCatalog() { ... }
@Bean
@Qualifier("firstMovieCatalog")
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
-
作用于字段
@Component public class MovieRecommender { @Autowired @Qualifier("firstMovieCatalog") private MovieCatalog movieCatalog; // ... }
-
作用于参数
@Component public class MovieRecommender { private MovieCatalog movieCatalog; @Autowired public void MovieRecommender(@Qualifier("firstMovieCatalog") MovieCatalog movieCatalog) { this.movieCatalog = movieCatalog; } // ... }
实际上,即使没有添加
@Qualifier
注解,bean
也会存在默认限定符,即bean
名称。从本质上讲@Autowired
注解是通过限定符值来驱动依赖注入的,所以即使使用了默认限定符(bean
名称)其语义上始终都是通过限定符来缩小类型匹配的范围的。
需要注意,@Qualifier
注解所指定的限定符与bean id
所表示的唯一性有所不同,@Qualifier
限定符想表达的是特定组件的特征标识而不必须是唯一性标识,仅仅充当过滤bean
的标准。也就是说对于类型集合的依赖注入,我们可以将相同@Qualifier
限定符注入到一起(@Qualifier
适用于类型集合)。。
@Configuration
public class MyConfig {
@Bean
@Qualifier("injectBeanCollection")
public InjectBean firstInjectBean() { ... }
@Bean
@Qualifier("injectBeanCollection")
public InjectBean secondInjectBean() { ... }
@Bean
@Qualifier("injectBeanMap")
public InjectBean thirdInjectBean() { ... }
@Bean
@Qualifier("injectBeanMap")
public InjectBean fourInjectBean() { ... }
// ...
}
@Component
public class SimpleBean {
@Autowired
@Qualifier("injectBeanCollection")
private Collection<InjectBean> injectBeanCollection;
@Autowired
@Qualifier("injectBeanMap")
private Map<String, InjectBean> injectBeanMap;
}
如上述例子所示,IoC
容器会将firstInjectBean
和secondInjectBean
注入到injectBeanCollection
,而thirdInjectBean
和fourInjectBean
注入到injectBeanMap
。
实际上,
Spring
并不首推将@Autowired
注解应用于通过名称来标识需要注入的依赖,而是更推荐使用JSR-250
的@Resource
注解。因为虽然@Resource
注解与@Autowired
注解作用相似,但是语义上却有所不同。@Resource
注解在语义上定义为通过其唯一名称标识特定目标组件(与类型无关);而@Autowired
注解的语义则为在按类型选出候选的bean
后,再根据所指定限定符从中选出匹配的候选者(与类型有关)。更多详情可阅读一下资料:
@Resource
对于@Resource
注解(JSR-250
标准),它主要用于标记当前应用程序所需要的资源(例如,bean
依赖)。在Spring
中,IoC
容器会对标注了@Resource
注解的属性字段和配置方法(包含Setter
方法,但不包含构造方法)执行依赖注入。
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Resource(name="oneInjectBean")
public void setOneInjectBean(OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
@Resource(name="twoInjectBean")
public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
this.twoInjectBean = twoInjectBean;
}
}
@Component
public class SimpleBean {
@Resource(name="oneInjectBean")
private OneInjectBean oneInjectBean;
@Resource(name="twoInjectBean")
private TwoInjectBean twoInjectBean;
}
其中,我们需要在@Resource
注解上设置name
属性来指定需要注入的bean
名称。若没有设置name
属性,Spring
则会根据字段名称或方法参数名称为其生成默认名称。另外,与@Autowired
类似的是如果无法通过名称寻找出相应的bean
,则会根据类型匹配出对应的bean
(primary
)。例如,下面oneInjectBean
字段会首先查找名称为oneInjectBean
的bean
,如果查找失败则会继续通过OneInjectBean
类型匹配出对应的bean
(primary
)。
@Component
public class SimpleBean {
@Resource
private OneInjectBean oneInjectBean;
}
更多详情可阅读:
@Value
对于@Value
注解,它主要用于向被标注了注解的属性字段和配置方法/构造方法参数提供值表达式,通过它我们就可以轻易地完成表达式驱动或属性驱动的依赖注入。一般,我们会@Value
注解中使用SpEL
(Spring Expression Language
)表达式来注入值(#{systemProperties.myProp}
风格或者${my.app.myProp}
风格)。
@Component
public class SimpleBean {
@Value("#{systemProperties['user.catalog'] + 'Catalog' }")
private String injectValueString;
@Value("#{{'Thriller': 100, 'Comedy': 300}}")
private Map<String, Integer> injectValueMap;
}
@Component
public class SimpleBean {
@Value("${properties.injectValue}")
private String injectValueString;
// 提供默认值
@Value("${properties.injectValue:defaultValueString}")
private String injectDefaultValueString;
}
对于${my.app.myProp}
风格的属性占位符是通过Spring
提供的一个默认较宽松的嵌入值解析器来实现的,使用此解析器会尝试解析属性值,在无法解析的情况下会将属性名称(也就是属性key
)作为值注入。如果我们想对不存在的值保持较为严格的控制,应该声明一个PropertySourcesPlaceholderConfigurer
类型的bean
。
@Configuration
public class AppConfig {
// 通过这种方式配置的PropertySourcesPlaceholderConfigurer,@Bean方法必须是静态的
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
通过上述配置我们可以确保Spring
在初始化时将所有占位符都解析成功,如果存在任何占位符不能被解析则会初始化失败并抛出异常。
在
Spring Boot
中会默认配置一个PropertySourcesPlaceholderConfigurer
类型的bean
,通过它我们就可以从application.properties
和application.yml
文件中读取属性配置了。另外需要注意,因为@Value
注解的处理实际是在BeanPostProcessor
中执行的,所以我们不能在BeanPostProcessor
或BeanFactoryPostProcessor
中使用@Value
注解。更多详情可阅读一下资料:
@PostConstruct
/@PreDestroy
对于@PostConstruct
/@PreDestroy
注解,它们主要用于标注生命周期的初始化回调方法和销毁回调方法。
@Component
public class SimpleBean {
@PostConstruct
public void init() {
// initialization...
}
@PreDestroy
public void destroy() {
// destruction...
}
}
在使用@PostConstruct
/@PreDestroy
注解前,我们需要将CommonAnnotationBeanPostProcessor
注册到IoC
容器让其生效。CommonAnnotationBeanPostProcessor
不仅可以识别JSR-250
生命周期相关的注解:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
(在Spring 2.5
中引入),还能识别和处理@Resource
注解。
更多详情可阅读如下资料:
JSR 330
注解
自Spring3.0
开始,Spring
提供了对JSR-330
标准注解(依赖注入)的支持,这些注解会被Spring
以相同的方式进行扫描的。
对于JSR-330
标准注解的使用,我们必须添加相关的Jar
包。而如果使用Maven
,则可以添加如下依赖到pom.xml
配置文件中:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Inject
对于@Inject
注解,它主要作用与@Autowired
相似(可将@Autowired
替代为@Inject
),即标记需要自动装配的依赖(标记注入点)。其中@Inject
注解可用在属性字段、配置方法和构造方法上。
import javax.inject.Inject;
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Inject
private ThirdInjectBean thirdInjectBean;
@Inject
public SimpleBean(OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
@Inject
public void setTwoInjectBean(TwoInjectBean twoInjectBean){
this.twoInjectBean = twoInjectBean;
}
}
与@Autowired
注解相同,我们可以使用Provider
来声明bean
的依赖注入点,从而允许根据需要访问生命周期更短的bean
;或者允许通过Provider.get()
方法来延迟获取bean
实例。
import javax.inject.Inject;
import javax.inject.Provider;
@Component
public class SimpleBean {
private Provider<OneInjectBean> oneInjectBean;
private Provider<TwoInjectBean> twoInjectBean;
@Inject
public SimpleBean(Provider<OneInjectBean> oneInjectBean, Provider<TwoInjectBean> twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
与@Autowired
注解相同,我们可以使用java.util.Optional
和@Nullable
来标识需注入的依赖可为空。
而相比于
@Autowired
,java.util.Optional
和@Nullable
可能更适用在@Inject
上,因为@Inject
注解并没有required
属性。
import javax.inject.Inject;
@Component
public class SimpleBean {
private Optional<OneInjectBean> oneInjectBean;
private TwoInjectBean twoInjectBean;
@Inject
public void setTwoInjectBean(Optional<OneInjectBean> oneInjectBean){
this.oneInjectBean = oneInjectBean;
}
@Inject
public void setTwoInjectBean(@Nullable TwoInjectBean twoInjectBean){
this.twoInjectBean = twoInjectBean;
}
}
与@Autowired
注解类似,我们可以配合JSR-330
标准提供的@Named
注解(作用与@Qualifier
注解类似)对注入bean
的限定符进行指定。
import javax.inject.Inject;
import javax.inject.Named;
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
@Inject
public SimpleBean(@Named("oneInjectBean") OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
}
更多详情可阅读如下资料:
@Named
/@ManagedBean
在JSR 330
的标准注解中,我们可以使用@javax.inject.Named
或javax.annotation.ManagedBean
来代替@Component
来标注容器中的组件。
import javax.inject.Inject;
import javax.inject.Named;
// 与@Component具有同样的特性:默认不指定名称同样可以
// @Named
// @ManagedBean
// @ManagedBean("simpleBean") could be used as well
@Named("simpleBean")
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Inject
public void setTwoInjectBean(OneInjectBean oneInjectBean){
this.oneInjectBean = oneInjectBean;
}
@Inject
public void setTwoInjectBean(TwoInjectBean twoInjectBean){
this.twoInjectBean = twoInjectBean;
}
}
与@Component
相反,JSR-330
中的@Named
和JSR-250
中的ManagedBean
注解的元注解衍生类并不能发挥作用,即不能通过元注解组合的方式衍生出新的业务注解。
更多详情可阅读如下资料:
Bean
的作用域(自定义)
Spring
的bean
作用域机制是可扩展的,我们不但可以定义自己的作用域,甚至还可以重新定义现有除了singleton
和prototype
的作用域(不推荐)。那么,如果我们要自定义bean
的作用域,则需要实现org.springframework.beans.factory.config.Scope
接口。
/**
* Strategy interface used by a {@link ConfigurableBeanFactory},
* representing a target scope to hold bean instances in.
* This allows for extending the BeanFactory's standard scopes
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON "singleton"} and
* {@link ConfigurableBeanFactory#SCOPE_PROTOTYPE "prototype"}
* with custom further scopes, registered for a
* {@link ConfigurableBeanFactory#registerScope(String, Scope) specific key}.
*
* <p>{@link org.springframework.context.ApplicationContext} implementations
* such as a {@link org.springframework.web.context.WebApplicationContext}
* may register additional standard scopes specific to their environment,
* e.g. {@link org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST "request"}
* and {@link org.springframework.web.context.WebApplicationContext#SCOPE_SESSION "session"},
* based on this Scope SPI.
*
* <p>Even if its primary use is for extended scopes in a web environment,
* this SPI is completely generic: It provides the ability to get and put
* objects from any underlying storage mechanism, such as an HTTP session
* or a custom conversation mechanism. The name passed into this class's
* {@code get} and {@code remove} methods will identify the
* target object in the current scope.
*
* <p>{@code Scope} implementations are expected to be thread-safe.
* One {@code Scope} instance can be used with multiple bean factories
* at the same time, if desired (unless it explicitly wants to be aware of
* the containing BeanFactory), with any number of threads accessing
* the {@code Scope} concurrently from any number of factories.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @since 2.0
* @see ConfigurableBeanFactory#registerScope
* @see CustomScopeConfigurer
* @see org.springframework.aop.scope.ScopedProxyFactoryBean
* @see org.springframework.web.context.request.RequestScope
* @see org.springframework.web.context.request.SessionScope
*/
public interface Scope {
/**
* Return the object with the given name from the underlying scope,
* {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
* if not found in the underlying storage mechanism.
* <p>This is the central operation of a Scope, and the only operation
* that is absolutely required.
* @param name the name of the object to retrieve
* @param objectFactory the {@link ObjectFactory} to use to create the scoped
* object if it is not present in the underlying storage mechanism
* @return the desired object (never {@code null})
* @throws IllegalStateException if the underlying scope is not currently active
*/
Object get(String name, ObjectFactory<?> objectFactory);
/**
* Remove the object with the given {@code name} from the underlying scope.
* <p>Returns {@code null} if no object was found; otherwise
* returns the removed {@code Object}.
* <p>Note that an implementation should also remove a registered destruction
* callback for the specified object, if any. It does, however, <i>not</i>
* need to <i>execute</i> a registered destruction callback in this case,
* since the object will be destroyed by the caller (if appropriate).
* <p><b>Note: This is an optional operation.</b> Implementations may throw
* {@link UnsupportedOperationException} if they do not support explicitly
* removing an object.
* @param name the name of the object to remove
* @return the removed object, or {@code null} if no object was present
* @throws IllegalStateException if the underlying scope is not currently active
* @see #registerDestructionCallback
*/
@Nullable
Object remove(String name);
/**
* Register a callback to be executed on destruction of the specified
* object in the scope (or at destruction of the entire scope, if the
* scope does not destroy individual objects but rather only terminates
* in its entirety).
* <p><b>Note: This is an optional operation.</b> This method will only
* be called for scoped beans with actual destruction configuration
* (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor).
* Implementations should do their best to execute a given callback
* at the appropriate time. If such a callback is not supported by the
* underlying runtime environment at all, the callback <i>must be
* ignored and a corresponding warning should be logged</i>.
* <p>Note that 'destruction' refers to automatic destruction of
* the object as part of the scope's own lifecycle, not to the individual
* scoped object having been explicitly removed by the application.
* If a scoped object gets removed via this facade's {@link #remove(String)}
* method, any registered destruction callback should be removed as well,
* assuming that the removed object will be reused or manually destroyed.
* @param name the name of the object to execute the destruction callback for
* @param callback the destruction callback to be executed.
* Note that the passed-in Runnable will never throw an exception,
* so it can safely be executed without an enclosing try-catch block.
* Furthermore, the Runnable will usually be serializable, provided
* that its target object is serializable as well.
* @throws IllegalStateException if the underlying scope is not currently active
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName()
* @see DestructionAwareBeanPostProcessor
*/
void registerDestructionCallback(String name, Runnable callback);
/**
* Resolve the contextual object for the given key, if any.
* E.g. the HttpServletRequest object for key "request".
* @param key the contextual key
* @return the corresponding object, or {@code null} if none found
* @throws IllegalStateException if the underlying scope is not currently active
*/
@Nullable
Object resolveContextualObject(String key);
/**
* Return the <em>conversation ID</em> for the current underlying scope, if any.
* <p>The exact meaning of the conversation ID depends on the underlying
* storage mechanism. In the case of session-scoped objects, the
* conversation ID would typically be equal to (or derived from) the
* {@link javax.servlet.http.HttpSession#getId() session ID}; in the
* case of a custom conversation that sits within the overall session,
* the specific ID for the current conversation would be appropriate.
* <p><b>Note: This is an optional operation.</b> It is perfectly valid to
* return {@code null} in an implementation of this method if the
* underlying storage mechanism has no obvious candidate for such an ID.
* @return the conversation ID, or {@code null} if there is no
* conversation ID for the current scope
* @throws IllegalStateException if the underlying scope is not currently active
*/
@Nullable
String getConversationId();
}
在Scope
接口中有四种方法:
-
Scope#get
方法从基础作用域返回对象:Object get(String name, ObjectFactory<?> objectFactory)
例如,会话作用域实现返回会话作用域的
bean
,如果不存在则会将bean
绑定session
后(以供将来引用)返回一个bean
的新实例。 -
Scope#remove
方法从基础作用域中删除对象:Object remove(String name)
例如,会话作用域实现从底层会话中删除会话作用域的
bean
并且返回该对象,如果没有找到指定名称的bean
实例则返回null
。 -
Scope#registerDestructionCallback
方法注册了一个回调,当此作用域对象被销毁时会调用此方法(回调):void registerDestructionCallback(String name, Runnable destructionCallback)
-
Scope#getConversationId
方法获取基础作用域的对话标识符:String getConversationId()
这个标识符对于每个作用域都是不同的。对于会话作用域的实现,此标识符可以是会话标识符。
在实现自定义Scope
实现后,我们需要通过ConfigurableBeanFactory#registerScope
方法向IoC
容器注册新的Scope
作用域。
void registerScope(String scopeName, Scope scope);
其中,registerScope
方法的第一个参数是与作用域关联的唯一名称,例如singleton
、prototype
等;registerScope
方法的第二个参数是向IoC
容器注册和使用的自定义Scope
实例。
为了便于理解,下面我们编写了一个自定义Scope
实现,并向IoC
容器完成注册。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
这样我们就可以创建遵守自定义Scope
范围规则的bean
定义了:
<bean id="..." class="..." scope="thread">
或
@Scope(scopeName="thread")
@Component
public class SimpleBean {
}
更多详情可阅读以下资料:
Bean
的后置处理器
BeanFactoryPostProcessor
BeanFactoryPostProcessor
是BeanFactory
的后置处理器。在BeanFactory
实例化后,IoC
容器会在BeanFactoryPostProcessor
中(如有)读取Metadata
配置,并在实例化任何bean
(除了BeanFactoryPostProcessor
)之前提供入口让其可对Metadata
配置进行修改。简单来说,BeanFactoryPostProcessor
即是可在容器实例化任何bean
(除了BeanFactoryPostProcessor
)前对读取的Metadata
配置进行修改。另外,如果存在多个BeanFactoryPostProcessor
进行处理时,我们可以通过继承接口Ordered
来指定其执行的顺序。
BeanFactoryPostProcessor
的作用域是容器级的,即BeanFactoryPostProcessor
仅仅在当前容器有效。- 在
BeanFactoryPostProcessor
中与bean
实例一起工作从技术上讲是可行的(例如,通过使用BeanFactory.getBean()
),但是这样做会导致过早实例化而违背了标准容器的生命周期,从而可能产生一些负面的影响,比如绕过了bean
后处理器BeanPostProcessor
的处理。BeanFactoryPostProcessor
的延迟初始化标记会被忽略,因为如果没有其他bean
引用BeanFactoryPostProcessor
的情况下该后处理器将根本不会被实例化,这并不是预期的结果(对于下文提及的BeanPostProcessors
同样如此)。
/**
* Factory hook that allows for custom modification of an application context's
* bean definitions, adapting the bean property values of the context's underlying
* bean factory.
*
* <p>Useful for custom config files targeted at system administrators that
* override bean properties configured in the application context. See
* {@link PropertyResourceConfigurer} and its concrete implementations for
* out-of-the-box solutions that address such configuration needs.
*
* <p>A {@code BeanFactoryPostProcessor} may interact with and modify bean
* definitions, but never bean instances. Doing so may cause premature bean
* instantiation, violating the container and causing unintended side-effects.
* If bean instance interaction is required, consider implementing
* {@link BeanPostProcessor} instead.
*
* <h3>Registration</h3>
* <p>An {@code ApplicationContext} auto-detects {@code BeanFactoryPostProcessor}
* beans in its bean definitions and applies them before any other beans get created.
* A {@code BeanFactoryPostProcessor} may also be registered programmatically
* with a {@code ConfigurableApplicationContext}.
*
* <h3>Ordering</h3>
* <p>{@code BeanFactoryPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
* {@link org.springframework.core.PriorityOrdered} and
* {@link org.springframework.core.Ordered} semantics. In contrast,
* {@code BeanFactoryPostProcessor} beans that are registered programmatically
* with a {@code ConfigurableApplicationContext} will be applied in the order of
* registration; any ordering semantics expressed through implementing the
* {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
* programmatically registered post-processors. Furthermore, the
* {@link org.springframework.core.annotation.Order @Order} annotation is not
* taken into account for {@code BeanFactoryPostProcessor} beans.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 06.07.2003
* @see BeanPostProcessor
* @see PropertyResourceConfigurer
*/
@FunctionalInterface
public interface BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
对于BeanFactoryPostProcessor
的使用,我们可以像其他bean
一样去部署。特别地,如果其声明在ApplicationContext
中时,ApplicationContext
会在适当的时候将其运行使得可以改变定义在容器内的Metadata
配置(如有)。
更多详情请阅读:
BeanPostProcessor
BeanPostProcessor
接口定义了两个接口方法让我们可以实现它来提供自定义实例化逻辑、依赖关系解析逻辑等(可覆盖容器的默认行为)。当我们配置了多个BeanPostProcessor
实例时,可以通过实现Ordered
接口并设置其中的order
属性来控制每个BeanPostProcessor
实例的执行顺序。
BeanPostProcessor
的作用域是容器级的,即BeanPostProcessor
仅仅在当前容器有效。也就是说,如果您在一个容器中定义BeanPostProcessor
,它只会对该容器中的bean
进行后置处理post-process
,而不会对另一个容器中的bean
进行后置处理post-process
。
/**
* Factory hook that allows for custom modification of new bean instances —
* for example, checking for marker interfaces or wrapping beans with proxies.
*
* <p>Typically, post-processors that populate beans via marker interfaces
* or the like will implement {@link #postProcessBeforeInitialization},
* while post-processors that wrap beans with proxies will normally
* implement {@link #postProcessAfterInitialization}.
*
* <h3>Registration</h3>
* <p>An {@code ApplicationContext} can autodetect {@code BeanPostProcessor} beans
* in its bean definitions and apply those post-processors to any beans subsequently
* created. A plain {@code BeanFactory} allows for programmatic registration of
* post-processors, applying them to all beans created through the bean factory.
*
* <h3>Ordering</h3>
* <p>{@code BeanPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
* {@link org.springframework.core.PriorityOrdered} and
* {@link org.springframework.core.Ordered} semantics. In contrast,
* {@code BeanPostProcessor} beans that are registered programmatically with a
* {@code BeanFactory} will be applied in the order of registration; any ordering
* semantics expressed through implementing the
* {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
* programmatically registered post-processors. Furthermore, the
* {@link org.springframework.core.annotation.Order @Order} annotation is not
* taken into account for {@code BeanPostProcessor} beans.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 10.10.2003
* @see InstantiationAwareBeanPostProcessor
* @see DestructionAwareBeanPostProcessor
* @see ConfigurableBeanFactory#addBeanPostProcessor
* @see BeanFactoryPostProcessor
*/
public interface BeanPostProcessor {
/**
* Apply this {@code BeanPostProcessor} to the given new bean instance <i>before</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>The default implementation returns the given {@code bean} as-is.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* Apply this {@code BeanPostProcessor} to the given new bean instance <i>after</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding {@code bean instanceof FactoryBean} checks.
* <p>This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other {@code BeanPostProcessor} callbacks.
* <p>The default implementation returns the given {@code bean} as-is.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
在IoC
容器启动时,它会自动检测在Metadata
声明的BeanPostProcessor
(继承了BeanPostProcessor
的bean
),然后将它们(所有)及其直接引用的bean
进行实例化并注册到容器中。后续容器在实例化其它bean
时会有序地调用BeanPostProcessor
实例对bean
实例进行前置处理postProcessBeforeInitialization()
和后置处理postProcessAfterInitialization()
,前者会在bean
实例在初始化前(在容器调用InitializingBean.afterPropertiesSet()
或init
方法前)对其进行操作或修改;而后者则会在bean
实例初始化后对其进行操作或修改。
实际上,在BeanPostProcessor
中我们可以对bean
实例执行任何操作,例如对bean
实例封装一层代理。我们经常使用Spring AOP
的一部分基础类就是通过BeanPostProcessor
对bean
实例封装动态代理实现的,不过需要注意的是对于这些bean
实例(无论是BeanPostProcessor
实例还是它直接引用的bean
实例)都是无法使用Spring AOP
进行处理的,即没有切面aspect
可以织入它们。若存在这样的情况(将需要织入BeanPostProcessor
的bean
注入到BeanPostProcessor
)将会看到如下日志信息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).
需要注意,如果我们是通过容器自动检测的方式对
BeanPostProcessor
进行注册和实例化(推荐),则必须将其声明类型限定为BeanPostProcessor
(含其实现类),不然容器将不能在创建其它bean
之前通过类型自动检查到它,从而导致部分bean
在实例化时无法被其所处理(BeanPostProcessor
需要比较早的进行初始化以至于可以在其他bean
实例化时对它们进行处理)。而如果我们想通过程序方式注册和实例化BeanPostProcessor
,也可以使用Spring
提供的ConfigurableBeanFactory#addBeanPostProcessor
方法来完成,不过需要注意的是通过程序的方式注册BeanPostProcessor
实例将不会遵循Ordered
接口的语意,其执行顺序将取决于其注册顺序。另外,相比于自动注册,通过程序方式注册的BeanPostProcessor
实例将在自动注册的BeanPostProcessor
之前执行。更多详情请阅读:
Bean
的方法注入
当协作bean
之间的生命周期有所不同时,通过简单的依赖注入就可能会出现问题。如果单例bean A
需要使用非单例(prototype
)bean B
,容器是无法在每次需要bean A
时都为bean A
提供新的bean B
实例,因为容器只创建单例bean A
一次,所以只有一次设置属性的机会。
一个解决方案是放弃控制反转。我们可以使用ApplicationContext
在每次bean A
需要时调用ApplicationContext#getBean
来请求容器获取Bean B
。
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
但是一般不推荐使用上述方式,因为这需要业务代码与Spring
框架耦合在一起。因此,Spring
提供了另一种高级功能Method Injection
,让我们可以干净利落地处理此情况。
Lookup Method Injection
Lookup Method Injection
是一种可覆盖bean
实例方法的能力。Lookup Method Injection
实现Method Injection
的方式主要是通过CGLIB
库中的字节码来动态生成重写了指定方法的子类,并且在所覆盖方法中返回所指定的(相同容器中的)bean
实例。
需要注意,因为
Lookup Method Injection
是通过CGLIB
动态代理来实现的,所以需要被覆盖的bean
和需要被覆盖的方法都不能是final
。另外,Lookup Method
不适用于工厂方法,特别是不适用于Configuration
类中的@Bean
方法。因为在这种情况下容器并不负责创建实例,所以无法动态创建运行时生成的即时子类。
这里以CommandManager
类为例,我们将通过Lookup Method Injection
的方式覆盖createCommand()
方法来获取bean
实例。
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
对于被注入的方法(即,被覆盖的方法)签名格式如下所示:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
需要注意,如果被注入的方法是
abstract
,那么在动态生成的子类中将会实现它;而如果被注入的方法是非abstract
,那么在动态生成的子类中将会覆盖原始类中的方法。
其中,对于Lookup Method Injection
我们同样可以以XML
的方式和以Java
的方式进行配置,下面将分别对其进行阐述:
-
通过
XML
的方式在
XML
方式的配置中,我们通过lookup-method
标签指定需要注入的方法createCommand
(例子),这样每次调用createCommand
方法时都会返回新的myCommand
实例(bean
实例)。需要注意,这里每次都返回新实例是因为作用域是prototype
;而如果作用域是singleton
,则每次返回的是相同的实例。<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="myCommand"/> </bean>
如果在
XML
基础上结合Java
注解的方式进行,那么我们可以使用@Lookup
注解来代替lookup-method
标签的声明(在需要注入的方法上标注@Lookup
注解)。<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"></bean>
public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup("myCommand") protected abstract Command createCommand(); }
上述我们可以看到
@Lookup
注解的属性上标注了被注入的bean
名称来指示容器执行对该bean
的查询和注入。而为了更简化地使用,我们也可以直接不加属性的标注@Lookup
注解,这样容器就会通过类型匹配的方式获取和注入目标bean
(解析方法返回类型)。<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"></bean>
public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup protected abstract Command createCommand(); }
-
通过
Java
的方式在
Java
的配置中,我们可以直接在@Component
类中通过@Lookup
注解来指定需要注入的方法,其中原理与上述一致。@Configuration public class MyConfig { @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } }
@Component public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup protected abstract Command createCommand(); }
当然,更好的做法是提供非
abstract
类和非abstract
方法,让其通过覆盖的方法进行注入。@Configuration public class MyConfig { @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } }
@Component public class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup public Command createCommand(){ return null; } }
除此之外,对于
@Bean
方法我们也可以通过内部类的方式来实现相同的功能。@Configuration public class MyConfig { @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } @Bean public CommandManager commandManager() { // return new anonymous implementation of CommandManager with createCommand() // overridden to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } } }
更多详情可阅读:
Arbitrary Method Replacement
除了通过Lookup Method Injection
实现方法注入外,我门还可以使用一种以替换的方式实现的方法注入(不太有用),即将bean
中的任意方法替换为另一个方法实现。在基于XML
的配置中,我们可以使用replace-method
标签来指定将现有方法替换为另一个方法。
这里以MyValueCalculator
类为例,我们将通过Arbitrary Method Replacement
的方式替换computeValue
方法。
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
首先,我们需要实现org.springframework.beans.factory.support.MethodReplacer
接口提供新的方法定义。
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
然后,在原有的bean
定义中添加<replaced-method/>
元素,指定MethodReplacer
实例(bean
实例)并在其中使用一个或多个<arg-type/>
标签来指示被替换方法的方法签名(仅当存在多个方法重载时才需要指定方法签名)。
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
这样,我们就完成了Arbitrary Method Replacement
方式的方法注入了。
更多详情可阅读:
More
Environment
Environment
(接口)表示当前应用环境的抽象,在Spring
定义中它包含两大模块:Profiles
和Properties
。
Profiles
相当于逻辑bean
定义分组的抽象(bean
在定义时可被分配一个profile
(通过XML
或者Java
注解)),只有profile
被激活分组才能将它所包含的bean
定义注册到容器中。我们可以使用(与Profiles
所关联的)Environment
决定默认的profile
和激活的profile
。Properties
相当于应用配置属性的抽象,其中属性来源可源于:properties files
、JVM system properties
、system environment variables
、JNDI
、servlet context parameters
、ad-hoc Properties objects
、Map objects
等。我们可以使用Environment
获取、解析和配置与其所关联的Properties
属性。
Profiles
通过Profiles
机制我们可以在不同环境Environment
中将不同的bean
注册到容器,例如:
- 对开发、测试和生产环境分别使用不同的数据源进行工作。
- 在生产环境注册监控设施,而在开发和测试环境下忽略监控设施的注册。
在使用上,我们仍然可以使用XML
和Java
注解的方式进行配置,不过由于篇幅的原因这里就不对XML
的方式展示了,有兴趣的读者可以查阅相关资料。那么,下面我们将围绕着Java
注解的方式展示说明。
在基于Java
注解的配置方式中,我们可以将@Profile
注解标注在类上或者@Bean
方法上,使得它们只有在对应的profile
被激活时才注册到容器中。
-
将
@Profile
注解标注在配置类如果将
@Profile
注解标注在@Configuration
类,那么它所包含的所有@Bean
方法和通过@Import
引入的类都只能在对应的profile
被激活时才能注册到容器中。@Configuration @Profile("development") public class DevConfig { @Bean public DataSource dataSource() { return ...; } } @Configuration @Profile("production") public class ProConfig { @Bean public DataSource dataSource() throws Exception { return ...; } }
-
将
@Profile
注解标注在@Bean
方法@Configuration public class AppConfig { @Bean("dataSource") @Profile("development") public DataSource devDataSource() { return ...; } @Bean("dataSource") @Profile("production") public DataSource proDataSource() throws Exception { return ...; } }
除了直接使用
@Profile
注解外,我们还能通过将@Profile
变成元注解来衍生出各种自定义的组合注解使其生效,例如直接使用如下的@Production
来代替@Profile("production")
:@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }
其中所设置的profile
值,我们可以将它设置为简单profile
名字(例如development
或者production
),也可以将它设置为profile
表达式。其中,通过profile
表达式我们就可以设置更多复杂的逻辑profile
去表达我们想要指定的注册范围,具体如下所示:
!
:profile
值的逻辑“否”(NOT
)&
:profile
值的逻辑“与”(AND
)|
:profile
值的逻辑“或”(OR
)
需要注意,我们不能在不使用括号的情况下直接使用混合
&
和|
,例如production & us-east | eu-central
是无效表达式,要将它改成production & (us-east | eu-central)
才能成立。
在完成上述对bean
的分组后我们就可以向Spring
指定我们需要激活的profile
了。而对激活profile
的指定存在几种不同的方式,最直接方式的就是使用Environment
接口(通过ApplicationContext
获取)来设置,即:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
另外,我们还可以通过spring.profiles.active
属性来指定需要激活的profile
(可以在system environment variables
或者JVM system properties
等配置中指定),例如:
-Dspring.profiles.active="development"
对于激活
profile
的设置完全是可以多选的,例如:
通过
Environment
接口设置多个激活profile
(用逗号(,
)隔开):ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
通过
spring.profiles.active
属性设置多个激活profile
(用逗号(,
)隔开):-Dspring.profiles.active="profile1,profile2"
需要注意,如果我们没有设置激活profile
,那么可能会由于某些bean
的缺失导致Spring
在启动时或者在运行时发生错误。对于这种情况,我们也可以这是默认的profile
,即如果没有显式设置激活profile
,那么默认profile
组内的bean
就会被创建;否则默认profile
组内的bean
就会被忽略。默认情况下,对于默认profile
的值为default
,我们也可以使用Environment#setDefaultProfiles
方法或者设置spring.profiles.default
属性对其进行修改。
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return ...;
}
}
更多详情可阅读:
Properties
在Properties
模型中会将不同的属性来源(PropertySource
)组合为层级结构(可配置的),但在访问时我们不需要管每个配置在具体哪个层级,而是使用Spring
的Environment
接口(抽象)来方法,即:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
在实现上,Spring
会将每个属性来源构造为PropertySource
,也就是说在查询属性时实际上就是使用Environment
是在一系列PropertySource
中寻找出对应的属性。例如,Spring
中的StandardEnvironment
就包含了两个PropertySource
,其一为JVM system properties
属性集合(可通过System.getProperties()
获取);其二为system environment variables
属性集合(可通过System.getenv()
获取)。如果在JVM system properties
或者在system environment variables
中包含my-property
属性,那么env.containsProperty("my-property")
的调用就会返回true
。
对于
PropertySource
,我们可以将它看作是一个属性来源中的键值对(集合)。
需要注意,实际上Environment
是按照层级从上往下查询的,如果在上层PropertySource
中查询到结果则会立刻返回(顶层优先法则),而不会继续往下查询并执行类似于合并的操作(也可以看作是顶层的属性将底层的覆盖了)。
默认情况下,
JVM system properties
属性优先级高于system environment variables
属性。对于常见的
StandardServletEnvironment
,它完整的层级优先级如下所示(顶层是最高优先级):
- ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context)
- ServletContext parameters (web.xml context-param entries)
- JNDI environment variables (java:comp/env/ entries)
- JVM system properties (-D command-line arguments)
- JVM system environment (operating system environment variables)
除此之外,这种层级结构也是可配置的。比如,如果我们想将自定义的属性来源加入到Environment
的查询中(类似于配置中心),那么我们只需将自定义的PropertySource
实例加入到当前Environment
的PropertySources
集合即可。
ConfigurableApplicationContext ctx = new GenericApplicationContext();
// The MutablePropertySources API exposes a number of methods that allow for precise manipulation of the set of property sources.
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上述代码中,MyPropertySource
属性来源就已经被设置为Environment
查询的最高优先级。
或者我们也可以采用一种更简单的方式,即使用@PropertySource
注解将PropertySource
添加到Spring
的Environment
中。
/**
* Given a file called app.properties that contains the key-value pair testbean.name=myTestBean
*/
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
其中,对于@PropertySource
注解中的资源路径中的占位符${…}
,Spring
也可以使用已经注册到Environment
中的属性来源(PropertySource
)来进行解析。
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
上述配置中,如果my.placeholder
属性已经随着优先级更高的属性来源(PropertySource
)被注册到Environment
中,它将会得到解析;否则my.placeholder
属性会使用默认值default/path
(如果不存在默认值,则会抛出IllegalArgumentException
异常)。
更多详情可阅读:
源码解析
Bean
的创建
在Spring
中,对Bean
的实例化是在使用BeanFactory#getBean
方法获取bean
时完成的。在实现上,BeanFactory
会委托于AbstractBeanFactory#doGetBean
进一步处理:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
/**
* Return an instance, which may be shared or independent, of the specified bean.
* @param name the name of the bean to retrieve
* @param requiredType the required type of the bean to retrieve
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @param typeCheckOnly whether the instance is obtained for a type check,
* not for actual use
* @return an instance of the bean
*/
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// transformedBeanName方法的转换主要应用于FactoryBean,
// 在实现上它会去除name中所有的&前缀并赋值给beanName
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ...
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Check if bean exists circular reference.
// ...
// Check if bean definition exists in this factory.
// ...
// ...
// 经过此步骤的BeanDefinition可被称之为MergedBeanDefinition
// 将给定的bean转化为RootBeanDefinition,当bean定义存在继承关系则会合并它的parent并返回RootBeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // getMergedBeanDefinition方法用于处理bean的继承特性,
// 检查bean定义是否抽象
// ...
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
// 假设dependsOn不为空
for (String dep : dependsOn) {
// 检查是否有循环依赖
...
// 实例化依赖
getBean(dep);
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
// 假设没有异常
return createBean(beanName, mbd, args);
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = createBean(beanName, mbd, args);
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
// 假设scopeName是有效的
String scopeName = mbd.getScope();
// 假设scope存在
Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, () -> {
// 假设没有异常
return createBean(beanName, mbd, args);
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
}
在AbstractBeanFactory#doGetBean
中,首先会在单例作用域缓存中检测是否存在请求的bean
实例,如果存在会直接从缓存中获取;否则会按照下列步骤对其进行创建:
- 获取/创建
MergedBeanDefinition
- 获取/创建
depends-on
指定的bean
- 获取/创建
MergedBeanDefinition
对应的bean
- 获取/创建
bean
中真正的实例对象 - 适配/转换
bean
为目标的实例类型
MergedBeanDefinition
(逻辑概念),表示的是一种被合并出来的BeanDefinition
,其主要作用是处理存在继承关系的bean
定义(BeanDefinition
)。在AbstractBeanFactory#doGetBean
中,Spring
会直接通过getMergedLocalBeanDefinition
方法来获取最终已处理完继承逻辑的BeanDefinition
(若有继承关系,即MergedBeanDefinition
);在实现上,getMergedLocalBeanDefinition
方法会根据原始的BeanDefinition
及其父BeanDefinition
(如有)生成对应的副本,并最终返回RootBeanDefinition
(也正因如此,Spring
将这种合并出来的BeanDefinition
称为MergedBeanDefinition
(逻辑概念))。
我们可以看到无论是单例作用域还是其它类型的作用域(含prototype
作用域)它们都会在作用域缓存中获取实例失败后使用AbstractBeanFactory#createBean
方法对bean
进行实例化。其中,AbstractBeanFactory#createBean
方法为抽象方法,它的默认实现为AbstractAutowireCapableBeanFactory#createBean
。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
/**
* Create a bean instance for the given merged bean definition (and arguments).
* The bean definition will already have been merged with the parent definition
* in case of a child definition.
* <p>All bean retrieval methods delegate to this method for actual bean creation.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param args explicit arguments to use for constructor or factory method invocation
* @return a new instance of the bean
* @throws BeanCreationException if the bean could not be created
*/
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException;
}
/**
* ...
*
* <p>Provides bean creation (with constructor resolution), property population,
* wiring (including autowiring), and initialization. Handles runtime bean
* references, resolves managed collections, calls initialization methods, etc.
* Supports autowiring constructors, properties by name, and properties by type.
*
* ...
*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
//...
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// ...
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
// To resolve circular references
// ...
// Initialize the bean instance.
populateBean(beanName, mbd, instanceWrapper);
Object exposedObject = initializeBean(beanName, bean, mbd);
// To resolve circular references
// ...
// Register bean as disposable.
// ...
return exposedObject;
}
}
前置知识:
为了更充分的理解
bean
的实例化过程,在分析AbstractBeanFactory#createBean
方法前,我们先来看一下Spring
在bean
的实例化过程存在的一些扩展点,具体如下所述:
BeanPostProcessor
BeanPostProcessor
的语义中允许我们可以在bean
执行初始化(initialization
)前后对bean
进行前置处理(BeanPostProcessor#postProcessBeforeInitialization
)和后置处理(进行前置处理(BeanPostProcessor#postProcessAfterInitialization
)。一般情况下(典型),我们可以在前置处理中对bean
实例进行属性的填充;而在后置处理中对bean
实例进行代理的封装。
InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor
是BeanPostProcessor
的扩展子接口,它在BeanPostProcessor
语义的基础上新增了对bean
实例化(instantiation
)前后的扩展点,即实例化前置处理(InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
)和实例化后置处理(InstantiationAwareBeanPostProcessor#postProcessProperties
),其中实例化的后置处理会发生在显式属性设置(applyPropertyValues
)前。一般情况下(典型),我们可以在前置处理中阻止特定bean
执行默认的实例化流程,例如对特定bean
(比如,延迟初始化的bean
)以代理的方式进行创建;而在后置处理中我们可以对bean
实例实现额外的注入策略,例如字段注入。除此之外,InstantiationAwareBeanPostProcessor
还提供了InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
扩展点让我们可以控制bean
是否能执行字段的自动注入(已通过构造器或者工厂方法完成实例化)。
SmartInstantiationAwareBeanPostProcessor
SmartInstantiationAwareBeanPostProcessor
是InstantiationAwareBeanPostProcessor
的扩展子接口,它在InstantiationAwareBeanPostProcessor
语义的基础上新增了更多可以干预bean
的扩展点,具体如下所示:
扩展点 具体作用 predictBeanType
预测最终从 postProcessBeforeInstantiation
返回的bean
类型,可用于判断出某些特定的进行处理。determineCandidateConstructors
决定给定 bean
可以使用的候选构造器,可用于决定bean
在实例化时所使用的构造器。getEarlyBeanReference
获取特定 bean
早期的访问引用(即在bean
实例完全初始化前提前获取),专用于解决循环依赖问题。一般情况下,对于
getEarlyBeanReference
方法所返回的实例应该与BeanPostProcessor#postProcessBeforeInitialization
和BeanPostProcessor#postProcessAfterInitialization
所返回的实例相等(引用)。
DestructionAwareBeanPostProcessor
DestructionAwareBeanPostProcessor
是BeanPostProcessor
的扩展子接口,它在BeanPostProcessor
语义的基础上新增了对bean
销毁前的扩展点,即销毁前置处理(DestructionAwareBeanPostProcessor#postProcessBeforeDestruction
)。一般情况下(典型),我们可以在前置处理中对bean
执行销毁回调,例如上文所提及的DisposableBean#destroy
方法和通过其它方式自定义的销毁方法。另外,DestructionAwareBeanPostProcessor
还提供了DestructionAwareBeanPostProcessor#requiresDestruction
扩展点,通过它我们可以决定给定的bean
实例是否执行DestructionAwareBeanPostProcessor
的销毁前置处理。
MergedBeanDefinitionPostProcessor
MergedBeanDefinitionPostProcessor
是BeanPostProcessor
的扩展子接口,它在BeanPostProcessor
语义的基础上新增了对合并的bean
定义(MergedBeanDefinition
)属性设置前的扩展点,即属性设置前置处理(MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
)。一般情况下(典型),我们可以通过MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
在bean
实例的后置处理器前准备一些元数据的缓存;或者在MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
方法中执行对bean
定义属性的修改。
MergedBeanDefinition
概念源自bean
继承特性的处理,实际上MergedBeanDefinition
只是一个逻辑概念,本质上还是一个RootBeanDefinition
,我们可以简单地将它看作是原始bean
定义的副本。由于在实例化过程中也涵盖了bean
继承特性的处理(在AbstractBeanFactory#doGetBean
实现中使用getMergedLocalBeanDefinition
方法获取最终RootBeanDefinition
时,如果存在bean
定义的继承关系则会处理父定义属性的继承),所以在最终用于创建bean
实例的BeanDefinition
也属于MergedBeanDefinition
。综上所述,在结合
Spring
提供的(上述提及的)各个扩展点后,最终执行流程如下所示:+------------------------------------------+ | InstantiationAwareBeanPostProcessor | | +----------------------------------+ | | | postProcessBeforeInstantiation | | | +----------------------------------+ | | +--------------------------------------+ | | | MergedBeanDefinitionPostProcessor | | | | +---------------+ | | | | | Instantiation | | | | | +---------------+ | | | | +----------------------------------+ | | | | | postProcessMergedBeanDefinition | | | | | +----------------------------------+ | | | +--------------------------------------+ | | +----------------------------------+ | | | postProcessProperties | | | +----------------------------------+ | +------------------------------------------+ | v +-------+-------+ | populateBean | +-------+-------+ | v +------------------+-------------------+ | BeanPostProcessor | | +----------------------------------+ | | | postProcessBeforeInitialization | | | +----------------------------------+ | | +---------------+ | | |Initialization | | | +---------------+ | | +----------------------------------+ | | | postProcessAfterInitialization | | | +----------------------------------+ | +--------------------------------------+
更多关于上述扩展点的详情可阅读以下资料:
对于bean
的创建,我们可以将它大致分为bean
的实例化和bean
的初始化,其中bean
的实例化主要包括bean
实例创建、bean
属性加载和bean
属性填充3
个部分;而bean
的初始化则是执行用户预设的初始化方法。下面我们将分点进行阐述:
除了使用预设的步骤创建
bean
实例,我们还可以通过Spring
提供的扩展点来自定义bean
的创建步骤。具体地,Spring
在创建bean
实例前会提供了一个前置处理点resolveBeforeInstantiation
方法让我们可以实现自定义的bean
创建步骤。一般情况下(典型),我们会使用它(前置处理)来阻止特定
bean
执行默认的实例化流程。例如,使用代理的方式来创建特定bean
(比如,延迟初始化的bean
)。/** * Apply before-instantiation post-processors, resolving whether there is a * before-instantiation shortcut for the specified bean. */ @Nullable protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; // ...优化避免无效遍历 // 假设存在InstantiationAwareBeanPostProcessor扩展点 Class<?> targetType = determineTargetType(beanName, mbd); if (targetType != null) { bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); if (bean != null) { bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } } return bean; } /** * Apply InstantiationAwareBeanPostProcessors to the specified bean definition * (by class and name), invoking their {@code postProcessBeforeInstantiation} methods. * <p>Any returned object will be used as the bean instead of actually instantiating * the target bean. A {@code null} return value from the post-processor will * result in the target bean being instantiated. */ @Nullable protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) { for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { Object result = bp.postProcessBeforeInstantiation(beanClass, beanName); if (result != null) { return result; } } return null; } @Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
在
resolveBeforeInstantiation
方法中,Spring
会在一系列InstantiationAwareBeanPostProcessors
上逐个执行其前置处理方法postProcessBeforeInstantiation
,并将首个非空返回值传递到一系列BeanPostProcessor
中逐个执行其后置处理方法postProcessAfterInitialization
,然后忽略后续的属性填充和初始化流程直接返回当前的处理结果。需要注意,若
InstantiationAwareBeanPostProcessors#postProcessBeforeInstantiation
返回非空实例即意味着bean
的实例化已经完成,并不会继续执行原有的实例化流程了。也正因如此,在通过InstantiationAwareBeanPostProcessors#postProcessBeforeInstantiation
获取到非空实例后会将其传递到一系列的BeanPostProcessor
执行初始化后置处理postProcessAfterInitialization
。
1. Bean
的实例化
1.1. 实例创建
对于bean
实例的创建,Spring
是通过createBeanInstance
方法来实现的。
/**
* Create a new instance for the specified bean, using an appropriate instantiation strategy:
* factory method, constructor autowiring, or simple instantiation.
*/
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// ...
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean...
// ...
// Candidate constructors for autowiring?
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
}
// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);
}
/**
* Instantiate the bean using a named factory method. The method may be static, if the
* mbd parameter specifies a class, rather than a factoryBean, or an instance variable
* on a factory object itself configured using Dependency Injection.
*/
protected BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}
/**
* "autowire constructor" (with constructor arguments by type) behavior.
* Also applied if explicit constructor argument values are specified,
* matching all remaining arguments with beans from the bean factory.
* <p>This corresponds to constructor injection: In this mode, a Spring
* bean factory is able to host components that expect constructor-based
* dependency resolution.
*/
protected BeanWrapper autowireConstructor(
String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {
return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}
/**
* Instantiate the given bean using its default constructor.
*/
protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
// 假设非方法注入
Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
在createBeanInstance
方法中主要使用了3
种(我们熟知的)方法来创建bean
实例,分别是:
- 通过指定工厂方法构建
- 通过指定构造器构建构建
- 通过默认构造器构建
其中,最为简单的无疑就是通过默认构造器来构建bean
实例了。在实现上,这种方式会从BeanDefinition
中获取Class
类型,然后通过反射机制来获取它的无参构造器并以此来创建出具体的实例对象。
public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// 假设非方法注入
Constructor<?> constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
// 假设无异常
final Class<?> clazz = bd.getBeanClass();
constructorToUse = clazz.getDeclaredConstructor();
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
// 通过反射生成实例对象
return BeanUtils.instantiateClass(constructorToUse);
}
}
而其他两种更为复杂的方式则会先使用当前BeanFactory
实例构造出ConstructorResolver
实例:
/**
* Delegate for resolving constructors and factory methods.
*
* <p>Performs constructor resolution through argument matching.
*/
class ConstructorResolver {
private final AbstractAutowireCapableBeanFactory beanFactory;
/**
* Create a new ConstructorResolver for the given factory and instantiation strategy.
* @param beanFactory the BeanFactory to work with
*/
public ConstructorResolver(AbstractAutowireCapableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
然后,再通过其中基于工厂方法的ConstructorResolver#instantiateUsingFactoryMethod
和基于构造器的ConstructorResolver#autowireConstructor
方法来执行bean
实例的创建,下面我们来看看它们是如何实现的:
基于构造器创建bean
实例
与通过默认构造器构建bean
实例类似,在基于指定构造器创建bean
实例的ConstructorResolver#autowireConstructor
方法中会在选出最终的构造器和构造参数后将它们传入instantiate
方法中通过反射机制实现bean
实例的创建。
/**
* Delegate for resolving constructors and factory methods.
*
* <p>Performs constructor resolution through argument matching.
*/
class ConstructorResolver {
private final AbstractAutowireCapableBeanFactory beanFactory;
/**
* "autowire constructor" (with constructor arguments by type) behavior.
* Also applied if explicit constructor argument values are specified,
* matching all remaining arguments with beans from the bean factory.
* <p>This corresponds to constructor injection: In this mode, a Spring
* bean factory is able to host components that expect constructor-based
* dependency resolution.
*/
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);
// 最终选出的构造器
Constructor<?> constructorToUse = ...;
// 最终选出的构造参数
Object[] argsToUse = ...;
// ...
bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
return bw;
}
private Object instantiate(
String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse) {
// 假设无异常
InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
}
}
public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
final Constructor<?> ctor, Object... args) {
// 假设非方法注入
return BeanUtils.instantiateClass(ctor, args); // 通过反射生成实例对象
}
}
不难看出,与无指定构造器构建的相比这种方式上加多了对构造器和构造参数的解析和获取,那么下面我们来看看这里是如何进行解析的:
为了更加方便分析源码逻辑,下面代码中将部分非必要的或者易于理解的场景的代码隐藏掉,其中包括但不限于: 异常处理场景、缓存处理场景、仅存在一个无参构造器场景(直接通过反射机制生成实例对象)、显式传入参数场景等。
// 最终选出的构造器
Constructor<?> constructorToUse = null;
// 最终选出的构造参数
Object[] argsToUse = null;
// 获取构造器候选者(假设已经排序完成)
Constructor<?>[] candidates = ...;
// ... 处理“仅存在一个无参构造器场景” (会直接返回)
// Need to resolve the constructor.
boolean autowiring = ...;
// 创建构造器对象
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();
// 获取构造器参数
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
// 解析构造器参数
int minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
int minTypeDiffWeight = Integer.MAX_VALUE;
for (Constructor<?> candidate : candidates) {
// 假设parameterCount >= minNrOfArgs
int parameterCount = candidate.getParameterCount();
if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
// Already found greedy constructor that can be satisfied ->
// do not look any further, there are only less greedy constructors left.
break;
}
// ...
// 解析构造器参数类型
Class<?>[] paramTypes = candidate.getParameterTypes();
// 解析构造器参数名称
String[] paramNames = ...;
// 创建参数持有器,其中会解析自动装配的bean引用
ArgumentsHolder argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
// 通过argsHolder与paramTypes获取类型差异权重
int typeDiffWeight = ...;
// Choose this constructor if it represents the closest match.
if (typeDiffWeight < minTypeDiffWeight) {
constructorToUse = candidate;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
}
// ...
}
在经过大量的精简后,构造器和构造参数的整个解析和获取流程显著凸显了出来,在这里我们将它分为5
个步骤:
- 获取所有构造器候选者
- 获取
bean
定义中声明的构造参数 - 解析
bean
定义中声明的构造参数 - 构建构造参数数组(传入最终构造器进行实例化)
- 选出最终的构造器和构造参数(不断地循环第
4
步和第5
步对构造器进行比较)
其中,在这里我们重点关注一下包含了构造器注入的第3
点和第4
点,即解析bean
定义中声明的构造参数和构建构造参数数组(含依赖自动注入的处理)。
-
解析
bean
定义中声明的构造参数在第
3
步中,Spring
主要使用了resolveConstructorArguments
方法对bean
定义中显式指定的构造参数进行解析,在实现上它会分别对索引匹配的构造参数和类型匹配的构造参数逐个进行解析,并最终将解析结果存储到ConstructorArgumentValues
中。其中,具体到参数值的解析是委托给了BeanDefinitionValueResolver#resolveValueIfNecessary
方法来完成,它在解析时如果遇到其他bean
引用则会使用BeanFactory#getBean
进行获取。/** * Resolve the constructor arguments for this bean into the resolvedValues object. * This may involve looking up other beans. * <p>This method is also used for handling invocations of static factory methods. */ private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { // ... BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); // 假设minNrOfArgs是正确的,无需更新 int minNrOfArgs = cargs.getArgumentCount(); // 处理索引匹配的参数 for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cargs.getIndexedArgumentValues().entrySet()) { // 假设index符合条件 int index = entry.getKey(); ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue(); // 解析参数值,对于其他bean的引用会使用BeanFactory#getBean进行获取 Object resolvedValue = valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); ConstructorArgumentValues.ValueHolder resolvedValueHolder = new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName()); // ... resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder); } // 处理类型匹配的参数 for (ConstructorArgumentValues.ValueHolder valueHolder : cargs.getGenericArgumentValues()) { // 解析参数值,对于其他bean的引用会使用BeanFactory#getBean进行获取 Object resolvedValue = valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); ConstructorArgumentValues.ValueHolder resolvedValueHolder = new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName()); // ... resolvedValues.addGenericArgumentValue(resolvedValueHolder); } return minNrOfArgs; }
至此,
Spring
完成了对bean
定义中显式指定的构造参数的解析。 -
构建构造参数数组
在第
4
步中,Spring
主要使用了createArgumentArray
方法对第3
步解析的参数值进行组装,其中通过索引匹配的构造参数优先级比通过类型匹配的构造参数高,即优先取通过索引匹配的构造参数。在遇到匹配不到解析值的参数,Spring
会使用自动装配的方式在IoC
容器中进行寻找和匹配(若配置了自动装配),其中对于自动装配参数的匹配是交由resolveAutowiredArgument
方法来完成,在解析时如果遇到其他bean
引用它也会使用BeanFactory#getBean
进行获取。/** * Create an array of arguments to invoke a constructor or factory method, * given the resolved constructor argument values. */ private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues, BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable, boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException { ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); // ... for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { Class<?> paramType = paramTypes[paramIndex]; String paramName = (paramNames != null ? paramNames[paramIndex] : ""); // Try to find matching constructor argument value, either indexed or generic. ConstructorArgumentValues.ValueHolder valueHolder = null; if (resolvedValues != null) { ConstructorArgumentValues.ValueHolder valueHolder = null; if (resolvedValues != null) { // 分别从索引匹配的结果(优先)和类型匹配的结果中获取值。 valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, ...); // ... } } if (valueHolder != null) { // ... 将valueHolder赋值到args中 } else { // 假设autowiring为true,即标记了自动装配 MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); // ... // 解析自动装配的参数值,在选出最终的引用后会使用BeanFactory#getBean进行获取(若需要) Object autowiredArgument = resolveAutowiredArgument( methodParam, beanName, autowiredBeanNames, converter, fallback); // ... 将autowiredArgument赋值到args中 } } // ... return args; } /** * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. */ @Nullable public ValueHolder getArgumentValue(int index, @Nullable Class<?> requiredType, @Nullable String requiredName, @Nullable Set<ValueHolder> usedValueHolders) { // 优先使用索引匹配结果来获取值 ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName); if (valueHolder == null) { // 其次才使用类型匹配结果获取值 valueHolder = getGenericArgumentValue(requiredType, requiredName, usedValueHolders); } return valueHolder; }
至此,
Spring
完成了对bean
定义中自动装配的解析和对构造参数的组装。
基于工厂方法创建bean
实例
在基于工厂方法创建bean
实例的ConstructorResolver#instantiateUsingFactoryMethod
方法中会在选出最终的工厂实例、工厂方法和方法参数后将它们传入instantiate
方法中通过反射机制实现bean
实例的创建。
class ConstructorResolver {
private final AbstractAutowireCapableBeanFactory beanFactory;
/**
* Instantiate the bean using a named factory method. The method may be static, if the
* bean definition parameter specifies a class, rather than a "factory-bean", or
* an instance variable on a factory object itself configured using Dependency Injection.
* <p>Implementation requires iterating over the static or instance methods with the
* name specified in the RootBeanDefinition (the method may be overloaded) and trying
* to match with the parameters. We don't have the types attached to constructor args,
* so trial and error is the only way to go here. The explicitArgs array may contain
* argument values passed in programmatically via the corresponding getBean method.
*/
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);
// 最终选出的工厂实例
Object factoryBean = ...;
// 最终选出的工厂方法
Method factoryMethodToUse = ...;
// 最终选出的工厂方法参数
Object[] argsToUse = ...;
// ...
bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
return bw;
}
private Object instantiate(String beanName, RootBeanDefinition mbd,
@Nullable Object factoryBean, Method factoryMethod, Object[] args) {
// 假设无异常
InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
return strategy.instantiate(mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);
}
}
public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, final Method factoryMethod, Object... args) {
// 假设无异常
ReflectionUtils.makeAccessible(factoryMethod);
// 忽略其他逻辑
Object result = factoryMethod.invoke(factoryBean, args); // 通过反射调用工厂方法
// 假设result不为null
return result;
}
}
也许在XML
配置中,我们十分清晰通过工厂方法创建bean
的概念,但在Java
的配置方式中一般很难找到相关的概念。而实际上在Java
的配置方式也是存在对应的概念的,也就是我们通过@Bean
方法所声明的bean
。其中,如果@Bean
方法是实例方法则表示它是实例工厂方法,而如果@Bean
方法是静态方法则表示它是静态工厂方法,那么自然而然的它们所在的@Configuration
或者@Component
配置类则是它们的工厂类factory-class
或者工厂实例factory-bean
。下面我们可以看到@Bean
方法的解析代码:
/**
* Reads a given fully-populated set of ConfigurationClass instances, registering bean
* definitions with the given {@link BeanDefinitionRegistry} based on its contents.
*/
class ConfigurationClassBeanDefinitionReader {
/**
* Read the given {@link BeanMethod}, registering bean definitions
* with the BeanDefinitionRegistry based on its contents.
*/
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// ...
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
if (metadata.isStatic()) {
/**
* static @Bean method
*/
// 对于静态工厂方法仅需设置工厂类
if (configClass.getMetadata() instanceof StandardAnnotationMetadata sam) {
beanDef.setBeanClass(sam.getIntrospectedClass());
}
else {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
// 设置方法名字
beanDef.setUniqueFactoryMethodName(methodName);
}
else {
/**
* instance @Bean method
*/
// 对于实例工厂方法需设置工厂实例
beanDef.setFactoryBeanName(configClass.getBeanName());
// 设置方法名字
beanDef.setUniqueFactoryMethodName(methodName);
}
// 设置方法对象
if (metadata instanceof StandardMethodMetadata sam) {
beanDef.setResolvedFactoryMethod(sam.getIntrospectedMethod());
}
// 设置为自动装配
beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
// ...
}
}
在loadBeanDefinitionsForBeanMethod
方法中我们可以发现在解析@Bean
时它会根据是否属于static
分别对静态工厂方法和实例工厂方法进行赋值,即对于静态工厂方法设置factory-class
(bean-class
)和对应的静态方法;而对于实例工厂方法则设置factory-bean
和对应的实例方法。另外,在loadBeanDefinitionsForBeanMethod
方法中并没有对@Bean
的方法参数进行解析,而是将autowire-mode
属性设置为AUTOWIRE_CONSTRUCTOR
,即在构造实例时通过自动装配的方式完成参数的赋值(可简单理解为使用构造参数自动装配的方式完成赋值)。
在了解这个概念后,下面我们再来看看Spring
是如何对它进行解析的:
为了更加方便分析源码逻辑,下面代码中将部分非必要的或者易于理解的场景的代码隐藏掉,其中包括但不限于: 异常处理场景、缓存处理场景、仅存在一个无参工厂方法场景(直接通过反射机制生成实例对象)、显式传入参数场景等。
// 最终选出的工厂实例
Object factoryBean = null; // 静态工厂方法的factoryBean为null
// 最终选出的工厂方法
Method factoryMethodToUse = null;
// 最终选出的工厂方法参数
Object[] argsToUse = null;
// factoryBeanName属性指的是工厂实例bean的名称,而不是FactoryBean实例的名称
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
//...
factoryBean = this.beanFactory.getBean(factoryBeanName);
}
// ...
// 获取工厂方法候选者
List<Method> candidates = ...;
// ... 处理“仅存在一个无参工厂方法场景” (会直接返回)
boolean autowiring = ...;
// 获取工厂方法参数
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
// 获取工厂方法参数
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();
// 解析工厂方法参数
int minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
int minTypeDiffWeight = Integer.MAX_VALUE;
for (Method candidate : candidates) {
// 假设parameterCount >= minNrOfArgs
int parameterCount = candidate.getParameterCount();
// 解析工厂方法参数类型
Class<?>[] paramTypes = candidate.getParameterTypes();
// 解析工厂方法参数名称
String[] paramNames = ...;
// 创建参数持有器,其中会解析自动装配的bean引用
ArgumentsHolder argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);
// 通过argsHolder与paramTypes获取类型差异权重
int typeDiffWeight = ...;
// Choose this factory method if it represents the closest match.
if (typeDiffWeight < minTypeDiffWeight) {
factoryMethodToUse = candidate;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
}
// ...
}
// ...
在经过大量的精简后,工厂方法和方法参数的整个解析和获取流程显著凸显了出来,在这里我们将它分为5
个步骤:
- 获取所有工厂方法候选者
- 获取
bean
定义中声明的工厂方法参数 - 解析
bean
定义中声明的工厂方法参数 - 构建工厂方法参数数组(传入最终工厂方法进行实例化)
- 选出最终的工厂方法和工厂方法参数(不断地循环第
4
步和第5
步对构造器进行比较)
如果阅读过上文关于“基于构造器创建bean
实例“模块的源码分析,不难发现对于第3
步和第4
步的逻辑几乎是相同的,所以在这里笔者就不再展开分析了。其中,根据上文对@Bean
工厂方法的解析我们可以得知对于@Bean
方法参数的解析是在createArgumentArray
方法中通过自动装配的方式完成的,而非通过resolveConstructorArguments
方法完成解析的,这是因为在@Bean
方法解析时并没有解析方法参数到ConstructorArgumentValues
中,而是将方法解析对象的autowire-mode
属性设置为AUTOWIRE_CONSTRUCTOR
,即使用自动装配完成参数值的设置。
也许有细心的读者在分析“基于工厂方法构建bean
实例”的过程中会发现它也采用了与“基于构造器构建bean
实例”相同的方法与概念,即带有构造器字眼的参数和方法:ConstructorArgumentValues
参数、ConstructorArgumentValues
参数和resolveConstructorArguments
方法等,这实际上是因为在工厂方法的执行过程中将方法参数充当了类似于构造器参数那样必须的存在,所以在逻辑相同的情况下复用了具有相同效果的参数与方法。与此同时,这样的设计方案也导致了在Spring
处理循环依赖时无法有效的解决工厂方法参数上的循环依赖。
需要注意的是,
Spring
在处理实例工厂方法时会首先通过BeanFactory#getBean
向IoC
容器请求(获取/创建)它的工厂实例(factory-bean
),而静态工厂方法则不会触发这一步骤。
1.2. 属性前置处理
在完成bean
实例的创建后,我们就可以进行属性的填充了。但是在属性填充前,Spring
还提供了applyMergedBeanDefinitionPostProcessors
方法让我们可以进行bean
的属性前置处理,即将BeanDefinition
传入每个MergedBeanDefinitionPostProcessor
中的postProcessMergedBeanDefinition
方法进行前置处理。
/**
* Apply MergedBeanDefinitionPostProcessors to the specified bean definition,
* invoking their {@code postProcessMergedBeanDefinition} methods.
* @param mbd the merged bean definition for the bean
* @param beanType the actual type of the managed bean instance
* @param beanName the name of the bean
* @see MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
*/
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);
}
}
1.3. 属性加载
在完成bean
属性的前置处理后,我们就顺着执行流程进入到populateBean
方法进行属性的填充了。
/**
* Populate the bean instance in the given BeanWrapper with the property values
* from the bean definition.
*/
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// ...
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// Add property values based on autowire by name if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// Add property values based on autowire by type if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
// ... 属性后置处理
// ... 检查依赖
// ... 属性填充
}
在populateBean
方法中Spring
会根据自动装配的类型AUTOWIRE_BY_NAME
(按照beanName
进行匹配)和AUTOWIRE_BY_TYPE
(按照beanType
进行匹配)分别调用autowireByName
方法和autowireByType
完成装配。其中,为了可以更清晰地分析bean
属性的加载过程,下面我们将基于autowireByName
方法来进一步分析。
/**
* Fill in any missing property values with references to
* other beans in this factory if autowire is set to "byName".
* @param beanName the name of the bean we're wiring up.
* Useful for debugging messages; not used functionally.
* @param mbd bean definition to update through autowiring
* @param bw the BeanWrapper from which we can obtain information about the bean
* @param pvs the PropertyValues to register wired objects with
*/
protected void autowireByName(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
if (containsBean(propertyName)) {
// 通过BeanFactory#getBean加载propertyName对应的bean
Object bean = getBean(propertyName);
pvs.add(propertyName, bean);
registerDependentBean(propertyName, beanName);
}
}
}
在autowireByName
方法中我们可以看到Spring
会对未加载的beanName
传入BeanFactory#getBean
中进行加载,并最终将它加入到MutablePropertyValues
中。
1.4. 属性后置处理
在完成bean
属性的加载后,Spring
提供了一个基于InstantiationAwareBeanPostProcessor
的扩展点postProcessProperties
使得我们可以对加载后的属性进行后置处理,即将PropertyValues
传入每个InstantiationAwareBeanPostProcessor
中的postProcessProperties
方法进行后置处理。
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// ...
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
// ... 属性加载
// 假设存在InstantiationAwareBeanPostProcessor扩展点
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
pvs = pvsToUse;
}
// ... 检查依赖
// ... 属性填充
}
1.5. 属性填充
在完成一系列的bean
属性处理后,Spring
就通过applyPropertyValues
执行属性的填充了,具体如下所示:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// ...
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
// ... 属性加载
// ... 属性后置处理
// ... 检查依赖
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
/**
* Apply the given property values, resolving any runtime references
* to other beans in this bean factory. Must use deep copy, so we
* don't permanently modify this property.
* @param beanName the bean name passed for better exception information
* @param mbd the merged bean definition
* @param bw the BeanWrapper wrapping the target object
* @param pvs the new property values
*/
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
// ...
MutablePropertyValues mpvs = ...;
List<PropertyValue> original = ...;
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, ...);
// Create a deep copy, resolving any references for values.
List<PropertyValue> deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
for (PropertyValue pv : original) {
// ... Add pv to deepCopy after converting it
deepCopy.add(pv after converting)
}
// ...
// Set our (possibly massaged) deep copy.
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
}
在applyPropertyValues
方法中,Spring
会将所有的PropertyValue
属性经过转换后添加到BeanWrapper
的propertyValues
属性后结束执行。
除此之外,如果我们想忽略
populateBean
方法中属性填充的执行,那么我们可以使用Spring
提供的一个扩展点InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
让我们可以提前终止bean
属性的填充,具体如下所示:protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { // ... // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. // 假设存在InstantiationAwareBeanPostProcessor扩展点 for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } // ... 属性加载 // ... 属性后置处理 // ... 检查依赖 // ... 属性填充 }
至此,bean
的实例化阶段完成。
2. Bean
的初始化
在完成bean
的实例化后,Spring
就开始执行initializeBean
方法完成bean
实例的初始化了。
/**
* Initialize the given bean instance, applying factory callbacks
* as well as init methods and bean post processors.
* <p>Called from {@link #createBean} for traditionally defined beans,
* and from {@link #initializeBean} for existing bean instances.
* @param beanName the bean name in the factory (for debugging purposes)
* @param bean the new bean instance we may need to initialize
* @param mbd the bean definition that the bean was created with
* (can also be {@code null}, if given an existing bean instance)
* @return the initialized bean instance (potentially wrapped)
*/
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
// 执行aware方法
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
// 假设可执行前置处理
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
// 假设没有异常
invokeInitMethods(beanName, wrappedBean, mbd);
// 假设可执行后置处理
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
在
bean
实例的初始化前,首先会使用Aware
接口注入部分bean
属性(具体可阅读invokeAwareMethods
方法),然后才真正开始bean
实例的初始化流程。
根据上述初始化代码的分析,我们很轻易的就可以将bean
的初始化流程分为3
个步骤,即:
- 初始化前置处理
- 初始化实例
- 初始化后置处理
为了更清晰地阐述bean
的初始化流程,下面笔者将分点逐个进行分析。
对于初始化的前置处理和后置处理
Spring
主要是通过BeanPostProcessor
来完成。对于BeanPostProcessor
,它所提供的能力在Spring
中常常发挥着重要的作用,例如常用的Spring
注解和Spring AOP
都是通过它来实现的。
2.1. 初始化前置处理
Bean
的初始化前置处理主要通过applyBeanPostProcessorsBeforeInitialization
方法完成。
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
在实现上,applyBeanPostProcessorsBeforeInitialization
方法中会将bean
传入每个BeanPostProcessor
中的postProcessBeforeInitialization
方法进行前置处理,直到遍历所有的BeanPostProcessor
或者在遇到第一个返回null
时结束并返回结果。
2.2. 初始化实例
Bean
的初始化处理主要是通过invokeInitMethods
方法完成,下面我们可以看到invokeInitMethods
方法的源码:
/**
* Give a bean a chance to react now all its properties are set,
* and a chance to know about its owning bean factory (this object).
* This means checking whether the bean implements InitializingBean or defines
* a custom init method, and invoking the necessary callback(s) if it does.
*/
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null && bean.getClass() != NullBean.class) {
String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
}
}
}
/**
* Invoke the specified custom init method on the given bean.
* Called by invokeInitMethods.
* <p>Can be overridden in subclasses for custom resolution of init
* methods with arguments.
*/
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
throws Throwable {
// 假设存在initMethod
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(bean.getClass(), initMethodName) :
ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass());
// 假设无异常
ReflectionUtils.makeAccessible(methodToInvoke);
methodToInvoke.invoke(bean);
}
根据对源码的分析,invokeInitMethods
方法主要可分为两大步骤:
- 首先会判断当前
bean
是否InitializingBean
,若当前bean
属于InitializingBean
并且其中的afterPropertiesSet
方法不属于外部管理(可简单理解为未使用@PostConstruct
标注),则直接执行它的InitializingBean#afterPropertiesSet
方法。 - 然后,再从
RootBeanDefinition
中获取自定义的initMethod
(通过配置属性指定的,具体可翻看上文),判断它是否属于InitializingBean
的afterPropertiesSet
方法或者被外部管理的initMethod
,若都不属于则通过反射逐个调用。
关于
RootBeanDefinition
中的externallyManagedInitMethods
属性用于记录被外部管理的的initMethod
,例如通过JSR-250
的@PostConstruct
注解所声明的initMethod
就属于被外部管理的的initMethod
。我们可以看到上述invokeInitMethods
方法中出现的hasAnyExternallyManagedInitMethod
方法是如何判断的:private Set<String> externallyManagedInitMethods; /** * Register an externally managed configuration initialization method — * for example, a method annotated with JSR-250's * {@link jakarta.annotation.PostConstruct} annotation. * <p>The supplied {@code initMethod} may be the * {@linkplain Method#getName() simple method name} for non-private methods or the * {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method) * qualified method name} for {@code private} methods. A qualified name is * necessary for {@code private} methods in order to disambiguate between * multiple private methods with the same name within a class hierarchy. */ public void registerExternallyManagedInitMethod(String initMethod) { if (this.externallyManagedInitMethods == null) { this.externallyManagedInitMethods = new LinkedHashSet<>(1); } this.externallyManagedInitMethods.add(initMethod); } /** * Determine if the given method name indicates an externally managed * initialization method, regardless of method visibility. * <p>In contrast to {@link #isExternallyManagedInitMethod(String)}, this * method also returns {@code true} if there is a {@code private} externally * managed initialization method that has been * {@linkplain #registerExternallyManagedInitMethod(String) registered} * using a qualified method name instead of a simple method name. */ boolean hasAnyExternallyManagedInitMethod(String initMethod) { if (isExternallyManagedInitMethod(initMethod)) { return true; } if (this.externallyManagedInitMethods != null) { for (String candidate : this.externallyManagedInitMethods) { // 对于全限定名称则直接截取最后的方法名进行判断 int indexOfDot = candidate.lastIndexOf('.'); if (indexOfDot >= 0) { String methodName = candidate.substring(indexOfDot + 1); if (methodName.equals(initMethod)) { return true; } } } } return false; } /** * Determine if the given method name indicates an externally managed * initialization method. * <p>See {@link #registerExternallyManagedInitMethod} for details * regarding the format for the supplied {@code initMethod}. */ public boolean isExternallyManagedInitMethod(String initMethod) { return (this.externallyManagedInitMethods != null && this.externallyManagedInitMethods.contains(initMethod)); }
在上述方法中主要是对
RootBeanDefinition
中的externallyManagedInitMethods
属性进行新增和匹配。其中,Spring
会使用registerExternallyManagedInitMethod
方法对它进行新增,使用hasAnyExternallyManagedInitMethod
方法或者isExternallyManagedInitMethod
方法将传入的initMethod
参数与externallyManagedInitMethods
属性进行比较并最终返回判断结果。
2.3. 初始化后置处理
Bean
的初始化后置处理主要通过applyBeanPostProcessorsAfterInitialization
方法完成。
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
在实现上,applyBeanPostProcessorsAfterInitialization
方法中会将bean
传入每个BeanPostProcessor
中的postProcessAfterInitialization
方法进行前置处理,直到遍历所有的BeanPostProcessor
或者在遇到第一个返回null
时结束并返回结果。
至此,bean
的初始化阶段完成。
关于
getObjectForBeanInstance
方法的调用在上文
doGetBean
方法的执行流程中,不难发现无论是从缓存中获取bean
还是主动创建bean
,它都会将获取到的bean
实例传递到getObjectForBeanInstance
方法中进一步处理,这其实是针对FactoryBean
所提供的特殊处理。在这里我们借着这个机会对FactoryBean
的实现原理一探究竟:在进入源码分析前,首先我们先来回顾一下上文对
FactoryBean
的描述:对于使用
FactoryBean
所声明的bean
,Spring
会将FactoryBean
及其FactoryBean#getObject
方法返回的对象都注册到IoC
容器中。其中,对于FactoryBean
指定或默认生成的名称是其FactoryBean#getObject
所创建的bean
实例名称,而FactoryBean
实例本身的名称则需在指定或默认生成的名称前加上前缀&
,即&beanName
。也就是说,对于使用
FactoryBean
所声明的bean
会产生如下结果:
- 类型为
FactoryBean
、名字为&beanName
的bean
实例。- 类型为
FactoryBean#getObject
、名字为beanName
的bean
实例。下面,我们将带着这两个结果的预期对
getObjectForBeanInstance
方法进行阅读与分析:public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { /** * Get the object for the given bean instance, either the bean * instance itself or its created object in case of a FactoryBean. * @param beanInstance the shared bean instance * @param name the name that may include factory dereference prefix * @param beanName the canonical bean name * @param mbd the merged bean definition * @return the object to expose for the bean */ protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { // 假设mbd不为空 // 判断name是否带有&前缀 if (BeanFactoryUtils.isFactoryDereference(name)) { // ... return beanInstance; } // Now we have the bean instance, which may be a normal bean or a FactoryBean. // If it's a FactoryBean, we use it to create a bean instance, unless the // caller actually wants a reference to the factory. if (!(beanInstance instanceof FactoryBean<?> factoryBean)) { return beanInstance; } // ... Object object = getObjectFromFactoryBean(factoryBean, beanName, ...); return object; } }
根据对代码的理解,对于
getObjectForBeanInstance
方法的入参主要有3
个需要注意的:
参数 说明 name
name
参数表示请求时所指定bean
的原始名称。其中,对于FactoryBean
则格式为&beanName
;对于FactoryBean#getObject
则格式为beanName
。beanName
beanName
参数表示请求时所指定的name
参数在经过处理后的名称。其中,对于FactoryBean
则格式为beanName
;对于FactoryBean#getObject
则格式为beanName
。beanInstance
beanInstance
参数表示通过beanName
参数请求获得的bean
实例。如果是FactoryBean
的定义,则表示FactoryBean
实例本身。不难得出,对于
name
参数值为&beanName
格式的请求,Spring
仅仅会将它用作是标识客户端向IoC
容器请求FactoryBean
实例本身而不是FactoryBean#getObject
返回的实例。在简化掉一些赋值语句与非核心判断逻辑后,
getObjectForBeanInstance
方法的整体流程就十分清晰了:
- 首先判断
name
参数是否带有&
前缀,如果是则返回beanInstance
实例,否则继续往下执行。在这一步中Spring
已经知道客户端是否需要获取FactoryBean
实例本身(Spring
定义了带有&
前缀的beanName
为FactoryBean
实例)。- 然后判断当前
beanInstance
是否属于FactoryBean
实例,如果不是则返回beanInstance
实例,否则继续往下执行。在这一步中Spring
已经完成普通bean
实例的处理(非FactoryBean
实例)。- 最后将
beanInstance
传入getObjectFromFactoryBean
方法获取FactoryBean#getObject
所返回的对象。在经过第1
步和第2
步的前置处理后,这一步中所处理的对象目标必然是FactoryBean#getObject
所返回的对象。接下来,我们再来看看
getObjectFromFactoryBean
方法是如何处理FactoryBean#getObject
所返回的对象的。/** * Support base class for singleton registries which need to handle * {@link org.springframework.beans.factory.FactoryBean} instances, * integrated with {@link DefaultSingletonBeanRegistry}'s singleton management. * * <p>Serves as base class for {@link AbstractBeanFactory}. */ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry { /** * Obtain an object to expose from the given FactoryBean. * @param factory the FactoryBean instance * @param beanName the name of the bean * @param shouldPostProcess whether the bean is subject to post-processing * @return the object obtained from the FactoryBean */ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, ...) { if (factory.isSingleton() && containsSingleton(beanName)) { // ...此处加了synchronized锁保证了这段代码的线程安全 Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { object = doGetObjectFromFactoryBean(factory, beanName); // Only post-process and store if not put there already during getObject() call above // (e.g. because of circular reference processing triggered by custom getBean calls) Object alreadyThere = this.factoryBeanObjectCache.get(beanName); if (alreadyThere != null) { object = alreadyThere; } else { // ... // 假设条件符合,执行初始化后置处理 object = postProcessObjectFromFactoryBean(object, beanName); if (containsSingleton(beanName)) { this.factoryBeanObjectCache.put(beanName, object); } } return object; } } else { Object object = doGetObjectFromFactoryBean(factory, beanName); // 假设条件符合,执行初始化后置处理 object = postProcessObjectFromFactoryBean(object, beanName); } return object; } /** * Obtain an object to expose from the given FactoryBean. * @param factory the FactoryBean instance * @param beanName the name of the bean * @return the object obtained from the FactoryBean */ private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException { Object object = factory.getObject(); return object; } } public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { /** * Applies the {@code postProcessAfterInitialization} callback of all * registered BeanPostProcessors, giving them a chance to post-process the * object obtained from FactoryBeans (for example, to auto-proxy them). * @see #applyBeanPostProcessorsAfterInitialization */ @Override protected Object postProcessObjectFromFactoryBean(Object object, String beanName) { return applyBeanPostProcessorsAfterInitialization(object, beanName); } @Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; } }
在
getObjectFromFactoryBean
方法中就有对FactoryBean#getObject
单例作用域与非单例作用域的处理了。具体地,这里分为两种情况:
单例作用域,即
FactoryBean
实例属于单例作用域,且FactoryBean#isSingleton
为true
的情况(与上文FactoryBean
单例作用域说明呼应)。
- 首先从
factoryBeanObjectCache
缓存中获取beanName
对应的实例(factoryBeanObjectCache
缓存专门用于存储FactoryBean#getObject
创建的对象),如果获取失败则继续往下执行。- 然后执行
doGetObjectFromFactoryBean
方法使用FactoryBean#getObject
创建bean
对象。- 接着再次从
factoryBeanObjectCache
缓存中获取beanName
对应的实例(用于处理循环依赖问题)。
- 如果能从
factoryBeanObjectCache
缓存中获取到,则使用缓存中的对象而不是使用FactoryBean#getObject
创建的。- 如果不能从
factoryBeanObjectCache
缓存中获取到,则确定使用FactoryBean#getObject
创建的对象,并对它执行以下步骤。
- 将实例传入初始化后置处理
BeanPostProcessor#postProcessAfterInitialization
方法执行。- 将结果放入到
factoryBeanObjectCache
缓存中(保证了FactoryBean#getObject
创建对象的单例作用域特性)。- 将获取/创建的
bean
对象返回。在上述第
3
步中重复再次从factoryBeanObjectCache
缓存中获取beanName
对应的实例主要是为了处理循环依赖所产生的问题,从而避免在单例作用域下返回各不相同的实例。需要注意,对于第3
步中重复地从factoryBeanObjectCache
缓存中获取实例会很容易让人误以为是针对多线程并发的处理,实际上因为if
代码块开头加入了synchronized
的锁机制,所以此处并不会发生并发的场景。下面,我们将结合例子对此作进一步阐述:@Component public class CircularBeanFactoryBean implements FactoryBean { @Autowired private ApplicationContext applicationContext; @Override public boolean isSingleton() { return true } @Override public Class getObjectType() { return CircularBean.class; } @Override public Object getObject() { CircularDependency dependency = applicationContext.getBean(CircularDependency.class); return new CircularBean(dependency); } } public class CircularBean { private CircularDependency circularDependency; public CircularReferenceBean(CircularDependency circularDependency) { this.circularDependency = circularDependency; } } @Component public class CircularDependency { @Autowired private CircularBean circularBean; }
在例子中,当
Spring
首次创建CircularBeanFactoryBean
实例后会通过getObjectFromFactoryBean
方法获取其中CircularBean
实例。按照下述执行步骤:
- 首先从
factoryBeanObjectCache
获取CircularBean
实例,显然当前并不存在CircularBean
实例。- 然后执行
doGetObjectFromFactoryBean
方法时使用FactoryBean#getObject
创建CircularBean
实例。
- 通过
ApplicationContext#getBean
获取CircularDependency
实例,而在获取/创建CircularDependency
实例的过程中会再次加载它的依赖CircularBean
(发生循环依赖),此时会再次触发getObjectFromFactoryBean
方法:
- 首先从
factoryBeanObjectCache
获取CircularBean
实例,因为在外层的尚未创建完CircularBean
实例,所以此时还是会获取失败。- 然后执行
doGetObjectFromFactoryBean
方法使用FactoryBean#getObject
创建CircularBean
对象。其中,FactoryBean#getObject
再次执行时触发的CircularDependency
实例获取(通过ApplicationContext#getBean
)由于Spring
对bean
循环依赖的解决方案使得此次直接可以从缓存中获取(而不是再次触发创建),最终将CircularDependency
实例传入CircularBean
构造函数完成CircularBean
实例的创建(第1
个CircularBean
实例被创建了)。- 接着将
CircularBean
实例放入到factoryBeanObjectCache
缓存中。- 最后将
CircularBean
实例返回并结束外层CircularDependency
依赖的创建。- 将
CircularDependency
依赖传入CircularBean
构造函数中完成CircularBean
实例的创建(第2
个CircularBean
实例被创建了,即此时存在2
个CircularBean
实例)。- 最后再次从
factoryBeanObjectCache
缓存中获取CircularBean
实例,此时由于在第2.1
步中已经将创建的CircularBean
实例存放到factoryBeanObjectCache
缓存中,所以此时能从缓存中获取到CircularBean
实例并使用它覆盖第2.2
步中创建的CircularBean
实例(如果不进行覆盖则会创建出2
个CircularBean
实例,这并不符合单例作用域的特性)。最终,
Spring
通过这种缓存的方式解决了单例作用域下FactoryBean
循环依赖所产生的多实例产生问题。非单例作用域(多例作用域)
相比于单例作用域,非单例作用域(多例作用域)的处理逻辑则简单很多,即每次都使用
doGetObjectFromFactoryBean
方法创建bean
实例,然后将实例传入初始化后置处理BeanPostProcessor#postProcessAfterInitialization
方法执行,最后将执行结果返回。总的来说,
FactoryBean
实例和FactoryBean#getObject
实例实际上所使用的beanName
都是相同的,只是它们存储的容器有所不同(特指单例作用域)。而Spring
在处理bean
请求时会根据命名中是否带有&
前缀来区分客户端是想获得/创建FactoryBean
实例本身还是FactoryBean#getObject
实例。另外需要注意的是,要将FactoryBean#getObject
返回的实例指定为单例作用域,不但需要让FactoryBean#isSingleton
设置为true
,而且还需要将FactoryBean
实例本身设置为单例作用域。
Bean
的注解解析
在Spring
中,对于声明式注解的解析基本上离不开BeanFactoryPostProcessor
和BeanPostProcessor
两大后置处理器,因此对于bean
注解的解析下文将分别通过BeanFactoryPostProcessor
和BeanPostProcessor
两大后置处理器为切入点详细展开。
CommonAnnotationBeanPostProcessor
对于@Resource
注解、@PostConstruct
注解和@PreDestroy
注解的解析与处理Spring
都是通过CommonAnnotationBeanPostProcessor
来完成。也就是说,如果我们要让@Resource
注解、@PostConstruct
注解和@PreDestroy
注解生效,就需要将CommonAnnotationBeanPostProcessor
注册到Spring
容器中。
/**
* ...
*
* <p>This post-processor includes support for the {@link javax.annotation.PostConstruct}
* and {@link javax.annotation.PreDestroy} annotations - as init annotation
* and destroy annotation, respectively - through inheriting from
* {@link InitDestroyAnnotationBeanPostProcessor} with pre-configured annotation types.
*
* <p>The central element is the {@link javax.annotation.Resource} annotation
* for annotation-driven injection of named beans, by default from the containing
* Spring BeanFactory, with only {@code mappedName} references resolved in JNDI.
* The {@link #setAlwaysUseJndiLookup "alwaysUseJndiLookup" flag} enforces JNDI lookups
* equivalent to standard Java EE 5 resource injection for {@code name} references
* and default names as well. The target beans can be simple POJOs, with no special
* requirements other than the type having to match.
*
* ...
*
* <p><b>NOTE:</b> A default CommonAnnotationBeanPostProcessor will be registered
* by the "context:annotation-config" and "context:component-scan" XML tags.
* Remove or turn off the default annotation configuration there if you intend
* to specify a custom CommonAnnotationBeanPostProcessor bean definition!
* <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus
* the latter configuration will override the former for properties wired through
* both approaches.
*
*/
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ... {
...
}
虽说@Resource
注解、@PostConstruct
注解和@PreDestroy
注解都是通过CommonAnnotationBeanPostProcessor
来使其生效,但实际上它们之间的实现原理确是有所不同,因此针对每个注解各自的生效原理下面将分点进行阐述(@PreDestroy
注解暂不分析)。
解析@Resource
对于@Resource
注解的解析,CommonAnnotationBeanPostProcessor
在初始化时首先会将Resource
注解类添加到resourceAnnotationTypes
属性上。
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ... {
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4);
static {
resourceAnnotationTypes.add(Resource.class);
// ...
}
}
然后,在bean
属性加载的后置处理postProcessProperties
方法(具体可阅读上文《源码分析:Bean
的创建》章节)中,Spring
会完成对被@Resource
注解标记字段和方法的依赖加载和依赖注入。
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
// 忽略异常处理
metadata.inject(bean, beanName, pvs);
return pvs;
}
在postProcessProperties
方法的实现上,Spring
会按照以下2
个步骤执行:
- 通过
findResourceMetadata
方法查询/构建出标记了注解的字段/方法。 - 通过
InjectedElement#inject
方法完成对被标记字段/方法的依赖加载与依赖注入。
-
1、查询/构建出标记了注解的字段/方法。
在
findResourceMetadata
方法中,Spring
会通过反射机制将bean
中被标注了@Resource
的字段/方法封装为ResourceElement
对象。private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) { // 忽略缓存处理 InjectionMetadata metadata = buildResourceMetadata(clazz); return metadata; } private InjectionMetadata buildResourceMetadata(final Class<?> clazz) { // ... List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); // 通过反射机制获取标注了注解的字段,并将他封装为ResourceElement并添加到currElements中 ReflectionUtils.doWithLocalFields(targetClass, field -> { // ... if (field.isAnnotationPresent(Resource.class)) { // 假设field为非静态字段 currElements.add(new ResourceElement(field, field, null)); } }); // 通过反射机制获取标注了注解的方法,并将他封装为ResourceElement添加到currElements中 ReflectionUtils.doWithLocalMethods(targetClass, method -> { // 可简单理解为method Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); // ... if (bridgedMethod.isAnnotationPresent(Resource.class)) { // 假设method为非静态方法 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new ResourceElement(method, bridgedMethod, pd)); } }); elements.addAll(0, currElements); // 由下至上遍历父类 targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return InjectionMetadata.forElements(elements, clazz); }
在实现上
findResourceMetadata
会将真正构建ResourceElement
对象的逻辑委托给buildResourceMetadata
来完成。在buildResourceMetadata
中会分别通过反射机制获取并封装bean
中被@Resource
注解标注的字段和方法,并最终返回InjectionMetadata
对象(含InjectionMetadata.InjectedElement
列表)。关于
Bridge method
,源自Java
泛型擦除的解决方案。在编译继承了泛型的类或者接口时,由于泛型擦除导致了方法覆盖失效,Java
会特地生成的与其泛型擦除后对应的方法,这个方法我们就称之为Bridge method
。更多详情我们可以阅读以下链接:
-
2、完成对被标记字段/方法的依赖加载与依赖注入。
在
ResourceElement
封装完成后,紧接着就是执行InjectedElement#inject
方法完成对bean
的加载与注入。而由于ResourceElement
并没有对其进行方法覆盖,因此执行的是InjectedElement
基类中定义的InjectedElement#inject
逻辑,即:/** * A single injected element. */ public abstract static class InjectedElement { protected final Member member; protected final boolean isField; @Nullable protected final PropertyDescriptor pd; protected InjectedElement(Member member, @Nullable PropertyDescriptor pd) { this.member = member; this.isField = (member instanceof Field); this.pd = pd; } /** * Either this or {@link #getResourceToInject} needs to be overridden. */ protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable { if (this.isField) { Field field = (Field) this.member; ReflectionUtils.makeAccessible(field); field.set(target, getResourceToInject(target, requestingBeanName)); } else { Method method = (Method) this.member; ReflectionUtils.makeAccessible(method); method.invoke(target, getResourceToInject(target, requestingBeanName)); } } /** * Either this or {@link #inject} needs to be overridden. */ @Nullable protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) { return null; } }
显然,在
InjectedElement#inject
中会通过Java
反射机制对字段属性进行赋值或者对方法进行调用。其中,对于属性值的获取和解析Spring
在这里是通过可继承的getResourceToInject
方法来完成,这就需要回到ResourceElement#getResourceToInject
的定义上了。public class CommonAnnotationBeanPostProcessor extends ... implements ... { private class ResourceElement extends LookupElement { @Override protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) { // 忽略Lazy的处理 return getResource(this, requestingBeanName); } } /** * Obtain the resource object for the given name and type. */ protected Object getResource(LookupElement element, @Nullable String requestingBeanName) ... { // 忽略JNDI的处理 // 通过BeanFactory获取和解析参数指定的依赖 return autowireResource(this.resourceFactory, element, requestingBeanName); } /** * Obtain a resource object for the given name and type through autowiring * based on the given factory. * @return the resource object (never {@code null}) */ protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) ... { // ... Object resource = BeanFactory#getBean(...); return resource; } }
最终,
Spring
经过层层委托执行BeanFactory#getBean
获取和解析参数所指定的依赖并返回到InjectedElement#inject
进行注入。
至此,在CommonAnnotationBeanPostProcessor
中完成了对@Resource
注解的解析与处理。
解析@PostConstruct
对于@PostConstruct
注解,表面上Spring
是通过CommonAnnotationBeanPostProcessor
来使其生效,但实际上是CommonAnnotationBeanPostProcessor
通过继承的InitDestroyAnnotationBeanPostProcessor
来使其最终发挥作用的。
在实现上,InitDestroyAnnotationBeanPostProcessor
并没有特殊指定@PostConstruct
注解作为标记初始化方法的注解,而是提供了InitDestroyAnnotationBeanPostProcessor#setInitAnnotationType
方法让客户端可以进行自定义设置。根据这个设定不难猜出,CommonAnnotationBeanPostProcessor
在继承InitDestroyAnnotationBeanPostProcessor
类的时候就向InitDestroyAnnotationBeanPostProcessor
设置了@PostConstruct
注解作为标记初始化方法的注解,具体如下所示:
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
* that supports common Java annotations out of the box, in particular the common
* annotations in the {@code jakarta.annotation} package. These common Java
* annotations are supported in many Jakarta EE technologies (e.g. JSF and JAX-RS).
*
* <p>This post-processor includes support for the {@link jakarta.annotation.PostConstruct}
* and {@link jakarta.annotation.PreDestroy} annotations - as init annotation
* and destroy annotation, respectively - through inheriting from
* {@link InitDestroyAnnotationBeanPostProcessor} with pre-configured annotation types.
*
* ...
*/
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ... {
/**
* Create a new CommonAnnotationBeanPostProcessor,
* with the init and destroy annotation types set to
* {@link jakarta.annotation.PostConstruct} and {@link jakarta.annotation.PreDestroy},
* respectively.
*/
public CommonAnnotationBeanPostProcessor() {
setInitAnnotationType(PostConstruct.class);
// ...
}
}
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, ... {
@Nullable
private Class<? extends Annotation> initAnnotationType;
/**
* Specify the init annotation to check for, indicating initialization
* methods to call after configuration of a bean.
* <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PostConstruct} annotation.
*/
public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
this.initAnnotationType = initAnnotationType;
}
}
在上述代码中我们可以看到,Spring
在构造CommonAnnotationBeanPostProcessor
时就将@PostConstruct
注解类设置到InitDestroyAnnotationBeanPostProcessor
的initAnnotationType
属性上了。
在完成了initAnnotationType
属性的初始化后,顺着bean
的创建流程执行到BeanPostProcessor
的前置处理方法postProcessBeforeInitialization
时,Spring
会通过InitDestroyAnnotationBeanPostProcessor#findLifecycleMetadata
方法查找出对应的生命周期方法,然后调用LifecycleMetadata#invokeInitMethods
进行初始化。
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
* that invokes annotated init and destroy methods. Allows for an annotation
* alternative to Spring's {@link org.springframework.beans.factory.InitializingBean}
* and {@link org.springframework.beans.factory.DisposableBean} callback interfaces.
*
* <p>The actual annotation types that this post-processor checks for can be
* configured through the {@link #setInitAnnotationType "initAnnotationType"}
* and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties.
* Any custom annotation can be used, since there are no required annotation
* attributes.
*
* <p>Init and destroy annotations may be applied to methods of any visibility:
* public, package-protected, protected, or private. Multiple such methods
* may be annotated, but it is recommended to only annotate one single
* init method and destroy method, respectively.
*
* <p>Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}
* supports the {@link jakarta.annotation.PostConstruct} and {@link jakarta.annotation.PreDestroy}
* annotations out of the box, as init annotation and destroy annotation, respectively.
*
* ...
*/
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, ... {
@Nullable
private Class<? extends Annotation> initAnnotationType;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
metadata.invokeInitMethods(bean, beanName);
return bean;
}
}
其中,在findLifecycleMetadata
方法会通过反射遍历类及其父类中标记了@PostConstruct
注解的方法,并将其封装为LifecycleMetadata
对象返回。
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, ... {
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
// 忽略缓存的处理
return buildLifecycleMetadata(clazz);
}
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
// ...
List<LifecycleElement> initMethods = new ArrayList<>();
Class<?> targetClass = clazz;
// 通过反射遍历类及其父类中标记了`@PostConstruct`注解的方法
do {
final List<LifecycleElement> currInitMethods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
}
});
initMethods.addAll(0, currInitMethods);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
// 假设存在
return new LifecycleMetadata(clazz, initMethods, destroyMethods));
}
/**
* Class representing information about annotated init and destroy methods.
*/
private class LifecycleMetadata {
private final Class<?> targetClass;
private final Collection<LifecycleElement> initMethods;
private volatile Set<LifecycleElement> checkedInitMethods;
public LifecycleMetadata(Class<?> targetClass, Collection<LifecycleElement> initMethods, ...) {
this.targetClass = targetClass;
this.initMethods = initMethods;
}
}
/**
* Class representing injection information about an annotated method.
*/
private static class LifecycleElement {
private final Method method;
public LifecycleElement(Method method) {
// 假设method符合条件
this.method = method;
}
// ...
}
}
在完成了LifecycleMetadata
对象(含标记有@PostConstruct
注解的方法)后,Spring
就会通过LifecycleMetadata#invokeInitMethods
方法完成对初始化方法的调用了(通过反射的方式完成方法的调用)。
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, ... {
/**
* Class representing information about annotated init and destroy methods.
*/
private class LifecycleMetadata {
private final Class<?> targetClass;
private final Collection<LifecycleElement> initMethods;
private volatile Set<LifecycleElement> checkedInitMethods;
public void invokeInitMethods(Object target, String beanName) throws Throwable {
Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;
Collection<LifecycleElement> initMethodsToIterate =
(checkedInitMethods != null ? checkedInitMethods : this.initMethods);
// 假设initMethodsToIterate不为空
for (LifecycleElement element : initMethodsToIterate) {
element.invoke(target);
}
}
}
/**
* Class representing injection information about an annotated method.
*/
private static class LifecycleElement {
private final Method method;
// ...
public void invoke(Object target) throws Throwable {
ReflectionUtils.makeAccessible(this.method);
this.method.invoke(target, (Object[]) null);
}
}
}
至此,Spring
在BeanPostProcessor
的前置处理方法postProcessBeforeInitialization
上完成了对被外部管理的initMethod
的调用(使用@PostConstruct
注解所标注的initMethod
方法)。
那对于
externallyManagedInitMethods
属性,Spring
是在什么时候对它进行修改来避免在invokeInitMethods
方法中被同时调用了呢?对于
externallyManagedInitMethods
属性的变更,实际上是InitDestroyAnnotationBeanPostProcessor
使用了MergedBeanDefinitionPostProcessor
提供的扩展点提前完成的(相对于BeanPostProcessor
),下面我们可以看到MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
方法:public class InitDestroyAnnotationBeanPostProcessor implements MergedBeanDefinitionPostProcessor, ... { @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { findInjectionMetadata(beanDefinition, beanType); } private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class<?> beanType) { LifecycleMetadata metadata = findLifecycleMetadata(beanType); metadata.checkConfigMembers(beanDefinition); return metadata; } /** * Class representing information about annotated init and destroy methods. */ private class LifecycleMetadata { private final Collection<LifecycleElement> initMethods; private volatile Set<LifecycleElement> checkedInitMethods; public void checkConfigMembers(RootBeanDefinition beanDefinition) { Set<LifecycleElement> checkedInitMethods = new LinkedHashSet<>(this.initMethods.size()); for (LifecycleElement element : this.initMethods) { String methodIdentifier = element.getIdentifier(); if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) { // 注册到externallyManagedInitMethods属性 beanDefinition.registerExternallyManagedInitMethod(methodIdentifier); checkedInitMethods.add(element); } } // ...忽略destroyMethod的处理 this.checkedInitMethods = checkedInitMethods; } } }
postProcessMergedBeanDefinition
方法首先会将处理委托给了findInjectionMetadata
方法,接着findInjectionMetadata
方法会提前完成findLifecycleMetadata
方法的调用,最后使用LifecycleMetadata
对象的checkConfigMembers
方法完成了externallyManagedInitMethods
属性的变更。需要注意,为了能够更清晰的理解初始化的逻辑,笔者贴出的上述代码是简化后的(例如,对于findLifecycleMetadata
方法由于存在多次的调用,实际上Spring
是做了缓存处理的),有兴趣的读者可以再去阅读Spring
的完整版源码。
最后,结合上文《源码分析:Bean
的创建》章节中提到Spring
使用激活bean
初始化的invokeInitMethods
方法中执行的顺序,即:
InitializingBean#afterPropertiesSet()
方法init()
方法(自定义配置)
在添加@PostConstruct
注解标记初始化方法后(Spring
将这种类型的init
方法称之为被外部管理的initMethod
),bean
整体的初始化方法执行顺序演变如下:
@PostConstruct
注解方法(被外部管理的initMethod
)InitializingBean#afterPropertiesSet()
方法init()
方法(自定义配置)
AutowiredAnnotationBeanPostProcessor
对于@Autowired
注解、@Inject
注解和@Value
注解的解析与处理Spring
都是通过AutowiredAnnotationBeanPostProcessor
来完成。也就是说,如果我们要让@Autowired
注解、@Inject
注解和@Value
注解生效,就需要将AutowiredAnnotationBeanPostProcessor
注册到Spring
容器中。
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}
* implementation that autowires annotated fields, setter methods, and arbitrary
* config methods. Such members to be injected are detected through annotations:
* by default, Spring's {@link Autowired @Autowired} and {@link Value @Value}
* annotations.
*
* <p>Also supports JSR-330's {@link javax.inject.Inject @Inject} annotation,
* if available, as a direct alternative to Spring's own {@code @Autowired}.
*
* ...
*
* <h3>Annotation Config vs. XML Config</h3>
* <p>A default {@code AutowiredAnnotationBeanPostProcessor} will be registered
* by the "context:annotation-config" and "context:component-scan" XML tags.
* Remove or turn off the default annotation configuration there if you intend
* to specify a custom {@code AutowiredAnnotationBeanPostProcessor} bean definition.
*
* ...
*
*/
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, ... {
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
// 忽略异常处理
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
}
}
首先,在AutowiredAnnotationBeanPostProcessor
构造器中就会将@Autowired
注解、@Inject
注解和@Value
注解的Class
类型添加到autowiredAnnotationTypes
中,然后在bean
属性加载的后置处理postProcessProperties
方法(具体可阅读上文《源码分析:Bean
的创建》章节)中,Spring
会完成对被标记字段和方法的依赖加载和依赖注入。
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
// 假设没有异常
metadata.inject(bean, beanName, pvs);
return pvs;
}
在postProcessProperties
方法中,Spring
会按照以下2
个步骤执行:
- 通过
findAutowiringMetadata
方法查询/构建出标记了注解的字段/方法。 - 通过
InjectionMetadata.InjectedElement#inject
方法完成对被标记字段/方法的依赖加载与依赖注入。
-
1、查询/构建出标记了注解的字段/方法。
在
findAutowiringMetadata
方法中,Spring
会通过反射机制获取并封装bean
中字段/方法为InjectionMetadata.InjectedElement
对象。private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) { // 忽略对缓存的处理 InjectionMetadata metadata = buildAutowiringMetadata(clazz); return metadata; } private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) { // ... List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); // 通过反射机制获取标注了注解的字段,并将他封装为InjectionMetadata.InjectedElement添加到currElements中 ReflectionUtils.doWithLocalFields(targetClass, field -> { // 查找并过滤出标注了注解的字段 MergedAnnotation<?> ann = findAutowiredAnnotation(field); if (ann != null) { // 假设field为非静态字段 boolean required = ...; currElements.add(new AutowiredFieldElement(field, required)); } }); // 通过反射机制获取标注了注解的方法,并将他封装为InjectionMetadata.InjectedElement添加到currElements中 ReflectionUtils.doWithLocalMethods(targetClass, method -> { // 可简单理解为method Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); // ... // 查找并过滤出标注了注解的方法 MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod); if (ann != null && ...) { // 假设method为非静态方法 boolean required = ...; PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new AutowiredMethodElement(method, required, pd)); } }); elements.addAll(0, currElements); // 由下至上遍历父类 targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return InjectionMetadata.forElements(elements, clazz); } @Nullable private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) { MergedAnnotations annotations = MergedAnnotations.from(ao); // 在AutowiredAnnotationBeanPostProcessor的构造器中@Autowired注解类、@Inject注解类和@Value注解类添加到autowiredAnnotationTypes属性中 for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) { MergedAnnotation<?> annotation = annotations.get(type); if (annotation.isPresent()) { return annotation; } } return null; }
在实现上
findAutowiringMetadata
会将真正构建InjectionMetadata.InjectedElement
对象的逻辑委托给buildAutowiringMetadata
来完成。在buildAutowiringMetadata
中会分别通过反射机制获取并封装bean
中被注解标注的字段和方法,并最终返回InjectionMetadata
对象(含InjectionMetadata.InjectedElement
列表)。关于
Bridge method
,源自Java
泛型擦除的解决方案。在编译继承了泛型的类或者接口时,由于泛型擦除导致了方法覆盖失效,Java
会特地生成的与其泛型擦除后对应的方法,这个方法我们就称之为Bridge method
。更多详情我们可以阅读以下链接:
-
2、完成对被标记字段/方法的依赖加载与依赖注入。
在完成对
bean
中字段/方法的封装后,Spring
接着会执行其中的InjectedElement#inject
方法完成对bean
的加载与注入。而封装字段的AutowiredFieldElement
类和封装方法的AutowiredMethodElement
类都对inject
方法进行了覆盖,下面我们来看看它是如何实现的:在
AutowiredFieldElement
中,Spring
首先会使用BeanFactory
来获取和解析字段中的依赖属性(如有),然后在使用反射机制完成属性值的注入。/** * Class representing injection information about an annotated field. */ private class AutowiredFieldElement extends InjectionMetadata.InjectedElement { private final boolean required; public AutowiredFieldElement(Field field, boolean required) { super(field, null); this.required = required; } @Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; // 解析属性值 Object value = resolveFieldValue(field, bean, beanName); if (value != null) { // 设置属性值 ReflectionUtils.makeAccessible(field); field.set(bean, value); } } @Nullable private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) { DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); // ... // 通过BeanFactory获取和解析指定的依赖 Object value = beanFactory.resolveDependency(desc, beanName, ...); return value; } }
与
AutowiredFieldElement
相似的,在AutowiredMethodElement
中Spring
会使用BeanFactory
对方法参数中依赖的属性(如有)逐个进行获取和解析,然后在使用反射机制将属性值传入方法中进行调用。/** * Class representing injection information about an annotated method. */ private class AutowiredMethodElement extends InjectionMetadata.InjectedElement { private final boolean required; public AutowiredMethodElement(Method method, boolean required, @Nullable PropertyDescriptor pd) { super(method, pd); this.required = required; } @Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { // 忽略缓存和异常的处理 Method method = (Method) this.member; // 解析方法参数 Object[] arguments = resolveMethodArguments(method, bean, beanName); if (arguments != null) { // 使用方法参数调用方法 ReflectionUtils.makeAccessible(method); method.invoke(bean, arguments); } } @Nullable private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) { int argumentCount = method.getParameterCount(); Object[] arguments = new Object[argumentCount]; // ... for (int i = 0; i < arguments.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); currDesc.setContainingClass(bean.getClass()); // 通过BeanFactory获取和解析指定的依赖 Object arg = beanFactory.resolveDependency(currDesc, beanName, ...); if (arg == null && !this.required) { arguments = null; break; } arguments[i] = arg; } return arguments; } }
除此之外,在
AutowiredAnnotationBeanPostProcessor
中还对标注在方法中的@Lookup
注解完成了解析。在实现上,Spring
会在determineCandidateConstructors
方法中通过反射机制获取标注了Lookup
注解的方法,然后将它添加到BeanFactory
中对应RootBeanDefinition
的methodOverrides
属性上。/* * <h3>{@literal @}Lookup Methods</h3> * <p>In addition to regular injection points as discussed above, this post-processor * also handles Spring's {@link Lookup @Lookup} annotation which identifies lookup * methods to be replaced by the container at runtime. This is essentially a type-safe * version of {@code getBean(Class, args)} and {@code getBean(String, args)}. * See {@link Lookup @Lookup's javadoc} for details. */ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, ... { @Override @Nullable public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException { // 忽略重复处理等判断 Class<?> targetClass = beanClass; do { ReflectionUtils.doWithLocalMethods(targetClass, method -> { Lookup lookup = method.getAnnotation(Lookup.class); if (lookup != null) { LookupOverride override = new LookupOverride(method, lookup.value()); RootBeanDefinition mbd = (RootBeanDefinition)this.beanFactory.getMergedBeanDefinition(beanName); mbd.getMethodOverrides().addOverride(override); } }); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); // ...处理构造器解析 } }
至此,在AutowiredAnnotationBeanPostProcessor
中完成了对@Inject
注解和@Value
注解的解析与处理。
Bean
的循环依赖
在Spring
的使用中常常会有bean
循环依赖的发生,貌似给人感觉就是这个概念是Spring
特有的,但实际上在软件工程中的反模式(anti-pattern
)中早已将臭名昭著的循循环依赖原则(ADP
)登记在册了,只不过在Spring
依赖注入的特性加持下循环依赖变得显而易见了,这也是大部分IoC
框架所能遇见的问题。
循环依赖的发生
简单来说,循环依赖即是指模块间的属性存在相互依赖的关系。典型地,在Spring
中我们可以轻松地找到以下触发到循环依赖的场景:
-
构造器自动装配发生循环依赖
@Service public class ServiceOne { private ServiceTwo serviceTwo; @Autowired public ServiceOne(ServiceTwo serviceTwo){ this.serviceTwo = serviceTwo; } } @Service public class ServiceTwo { private ServiceOne serviceOne; @Autowired public ServiceTwo(ServiceOne serviceOne){ this.serviceOne = serviceOne; } }
-
工厂方法自动装配发生循环依赖
@Bean public BeanA beanA(BeanB beanB) { return new BeanA(); } @Bean public BeanB BeanB(BeanA beanA) { return new BeanB(); } public class BeanA { } public class BeanB { }
-
Setter
方法自动装配发生循环依赖@Service public class ServiceOne { private ServiceTwo serviceTwo; @Autowired public void setServiceTwo(ServiceTwo serviceTwo){ this.serviceTwo = serviceTwo; } } @Service public class ServiceTwo { private ServiceOne serviceOne; @Autowired public void setServiceOne(ServiceOne serviceOne){ this.serviceOne = serviceOne; } }
-
字段自动装配发生循环依赖
@Service public class ServiceOne { @Autowired private ServiceTwo serviceTwo; } @Service public class ServiceTwo { @Autowired private ServiceOne serviceOne; }
其中,对于上述某些场景在发生循环依赖时可能会被Spring
顺利解决而避免异常的发生;对于另一些场景发生循环依赖时则可能无法被Spring
解决而抛出循环依赖异常。为了避免在使用Spring
的过程中由于使用不当触发到循环依赖异常,下面我们从Spring
对循环依赖的的解决方案入手在原理上认识到如何规避循环依赖异常的发生。
循环依赖的解决
在通过AbstractBeanFactory#doGetBean
方法执行bean
的实例化的时候,Spring
会在其中夹杂着对循环依赖的处理,以此来尽可能地避免在实例化过程中循环依赖异常的发生。那下面我们将从AbstractBeanFactory#doGetBean
方法开始对Spring
循环依赖的解决思路进行分析。
为了更清晰的阐述,这里将bean
的创建分为单例模式和多例模式2
种:
- 单例模式(
Singleton
)的bean
创建。 - 多例模式(
Prototype
)的bean
创建。
由于单例模式和多例模式下对循环依赖的处理方式基本涵盖了所有场景,我们完全可以结合单例模式和多例模式下对循环依赖的处理方式来完成对其他作用域的处理分析,因此在这里就不对
Spring
其他类型的作用域展示分析了。
单例模式的bean
创建
在单例模式下,doGetBean
方法首先会逐个从不同的缓存级别中获取bean
实例,如果其中某个层级不为空则将其取出并进一步处理后返回;而如果各个层级都不存在则表示当前bean
尚未被实例化,因此对该bean
执行实例化操作。下面,我们看到具体的代码步骤:
注意,在此章节贴出的代码并非完整代码,而是经过笔者简化后仅作用于实例化单例作用域
bean
的代码。
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws ... {
String beanName = ...;
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName, true);
if (sharedInstance != null && args == null) {
// ...
beanInstance = getObjectForBeanInstance(sharedInstance, ...);
}
else {
// ...
RootBeanDefinition mbd = ...;
// ...
// Create bean instance.
sharedInstance = getSingleton(beanName, () -> {
// 假设没有异常
return createBean(beanName, mbd, args);
});
beanInstance = getObjectForBeanInstance(sharedInstance, ...);
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
在doGetBean
方法中,首先会使用getSingleton(String, boolean)
方法在缓存中查找出对应的bean
实例,在查找失败的情况下再使用getSingleton(String, ObjectFactory)
方法创建对应的bean
实例。虽然这么一看其执行流程与常规的缓存策略大致相同,但实际上它会在常规缓存策略的基础上添加了多级缓存策略和构建重复性验证来避免循环依赖的发生。
在进行具体的执行流程之前,我们先来对Spring
在单例bean
的实例化过程中涉及到的多级缓存策略提前进行梳理。具体地,bean
在实例化过程中为保证单例作用域的特性、循环依赖的处理和一些设计理念的考虑一共使用到了3
个级别的缓存,即:
缓存 | 等级 | 说明 |
---|---|---|
singletonObjects |
三级缓存 | 缓存已完成的bean 实例,已执行完成bean 的初始化。 |
earlySingletonObjects |
二级缓存 | 缓存构建初期的bean 实例,尚未执行bean 实例的初始化。 |
singletonFactories |
一级缓存 | 缓存构建bean 的工厂实例,尚未执行bean 实例的创建。 |
根据上述对每个级别缓存的阐述,基本可以分析出:
- 在三级缓存
singletonObjects
的作用下保证了bean
单例作用域的特性; - 在二级缓存
earlySingletonObjects
的作用下完成了循环依赖的解决,这其实与常规的循环依赖解决方案大致相同,即通过缓存将尚未执行初始化的对象提前暴露出去; - 在一级缓存
singletonFactories
的作用下可以延迟二级缓存中earlySingletonObject
对象的生成(只有在循环依赖真正发生时才特意生成出earlySingletonObject
对象),毕竟循环依赖在bean
的引用关系中并不一定会发生(Spring
也不推荐在bean
的引用关系中发生循环依赖)。
当然,对各级缓存作用的分析仅是基于它们表面的代码行为,而对其在Spring
中真正发挥到的作用还需要进一步阅读doGetBean
方法的执行逻辑。那下面我们就开始展开对doGetBean
方法的阅读理解与逻辑分析。
在doGetBean
方法的执行流程中,Spring
首先会通过getSingleton(String, boolean)
方法从bean
的多级缓存中查询缓存实例:
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 忽略并发情况下的获取: Consistent creation of early reference within full singleton lock
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
其中,getSingleton(String, boolean)
方法正如上述所说会逐级查询缓存实例,若查询成功则在经过相应的处理后返回。具体地,getSingleton
会通过第2
个参数allowEarlyReference
来控制是否执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建。下面,我们将根据allowEarlyReference
参数不同的值总结出不同的执行流程:
-
当
allowEarlyReference
参数为true
,允许执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建:- 从三级缓存中查询已执行初始化完成的
bean
实例singletonObject
,如果查询存在直接返回(保证了bean
单例作用域的特性),否则执行第2
步。 - 从二级缓存中查询构建初期尚未执行初始化的
bean
实例earlySingletonObject
,如果查询存在直接返回(证实了bean
发生过循环依赖),否则执行第3
步。 - 从一级缓存中查询是否存在构建
bean
的工厂实例singletonFactory
,如果不存在直接返回null
(证实了bean
尚未被创建),否则:- 通过
singletonFactory
实例创建出earlySingletonObject
实例。 - 将
earlySingletonObject
实例添加到二级缓存中。 - 将
singletonFactory
实例从一级缓存中移除。 - 将
earlySingletonObject
实例返回。
- 通过
即,
getSingleton(String, true)
方法会分别从三级缓存singletonObjects
、二级缓存earlySingletonObjects
到一级缓存singletonFactorys
中查询对应的缓存实例,如果在三级缓存、二级缓存中查询成功则直接返回;如果在一级缓存中查询成功则根据工厂实例创建出earlySingletonObject
实例添加到二级缓存并返回该实例;如果都没有查询成功则返回null
表示尚未开始bean
实例的创建。+ | | v +---------+----------+ | | success | singletonObjects +------------>+ | | | +---------+----------+ | | | | | | failure | | | v | +-----------+------------+ | | | success | +---------->+ earlySingletonObjects +---------->+ | | | | | +-----------+------------+ | | | | | | | success | | failure | | | | | v | | +----------+-----------+ | | | | | +------------+ singletonFactories | | | | | +----------+-----------+ | | | | | | failure | | | v v null singletonObject/earlySingletonObject
- 从三级缓存中查询已执行初始化完成的
-
当
allowEarlyReference
参数为false
,不允许执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建:- 从三级缓存中查询已执行初始化完成的
bean
实例singletonObject
,如果查询存在直接返回(保证了bean
单例作用域的特性),否则执行第2
步。 - 从二级缓存中查询构建初期尚未执行初始化的
bean
实例earlySingletonObject
,如果查询存在直接返回(证实了bean
发生循环依赖),否则直接返回null
。
即,
getSingleton(String, false)
方法会分别从三级缓存singletonObjects
和二级缓存earlySingletonObjects
中查询对应的缓存实例,如果在三级缓存或者二级缓存中查询成功则直接返回,否则返回null
。+ | | v +---------+----------+ | | success | singletonObjects +------------>+ | | | +---------+----------+ | | | | | | failure | | | v | +-----------+------------+ | | | success | | earlySingletonObjects +---------->+ | | | +-----------+------------+ | | | | | | failure | | | v v null singletonObject/earlySingletonObject
- 从三级缓存中查询已执行初始化完成的
在代码中我们可以看到doGetBean
方法执行的是getSingleton(String, true)
方法,即会分别从三级缓存singletonObjects
、二级缓存earlySingletonObjects
到一级缓存singletonFactorys
中查询对应的缓存实例。为了能完整地描述循环依赖的整个流程,这里就暂且假设bean
尚未被创建过。因此,Spring
在执行getSingleton(String, true)
方法时返回null
,随即进入到getSingleton(String, ObjectFactory)
方法执行bean
实例的创建,具体如下所示:
/**
* Return the (raw) singleton object registered under the given name,
* creating and registering a new one if none registered yet.
* @param beanName the name of the bean
* @param singletonFactory the ObjectFactory to lazily create the singleton
* with, if necessary
* @return the registered singleton object
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// ...
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
// 假设无异常
singletonObject = singletonFactory.getObject();
newSingleton = true;
} finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
如同常规单例模式下的实例创建流程,在getSingleton(String, ObjectFactory)
方法中也会优先从三级缓存singletonObjects
中获取实例,在获取失败后才会执行singletonObject
对象的创建。而在singletonObject
的创建流程中,除了会完成singletonObject
实例的创建,还添加了实例创建的前置处理beforeSingletonCreation
与后置处理afterSingletonCreation
,具体代码如下所示:
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* Callback before singleton creation.
* <p>The default implementation register the singleton as currently in creation.
* @param beanName the name of the singleton about to be created
* @see #isSingletonCurrentlyInCreation
*/
protected void beforeSingletonCreation(String beanName) {
if (... && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
/**
* Callback after singleton creation.
* <p>The default implementation marks the singleton as not in creation anymore.
* @param beanName the name of the singleton that has been created
* @see #isSingletonCurrentlyInCreation
*/
protected void afterSingletonCreation(String beanName) {
if (... && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("...");
}
}
在前置处理beforeSingletonCreation
(创建bean
实例前)中,Spring
会尝试将当前beanName
加入到singletonsCurrentlyInCreation
中,若加入失败则抛出异常BeanCurrentlyInCreationException
;而后置处理afterSingletonCreation
(创建bean
实例后)中,Spring
会尝试将当前beanName
从singletonsCurrentlyInCreation
中移除,若移除失败则抛出异常IllegalStateException
。
实际上,
Spring
就是通过这种前置处理和后置处理来判断bean
在实例化过程中是否发生了无法解决的循环依赖,进而在请求时抛出异常提前阻止程序进入无限循环。
典型地,当Spring
在构造器自动装配时发生了循环依赖即会在前置处理中触发BeanCurrentlyInCreationException
异常的发生,具体流程如下图所示:
+
|
|
v
+-------------+-------------+
throw ex | | circular references
<------------+ beforeSingletonCreation +<----------------------+
| | |
+-------------+-------------+ |
| |
| |
| |
v |
+--------------+---------------+ +----------+-----------+
| | | |
| singletonFactory#getObject +--------->+ createBeanInstance |
| | | |
+--------------+---------------+ +----------+-----------+
| |
| | To expose earlySingletonObject
| |
v v
+------------+-------------+
| |
| afterSingletonCreation |
| |
+--------------------------+
回归到正常流程上,如果在前置处理beforeSingletonCreation
没有发现循环依赖,那么Spring
就会进入到createBean
方法执行bean
实例的创建。
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
//...
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
// 1. 执行`raw bean`实例的创建
BeanWrapper instanceWrapper = ...createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// ...
// 2. 通过`raw bean`实例构建工厂实例并将其添加到一级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
Object exposedObject = bean;
// 3. 执行`raw bean`实例的属性填充
populateBean(beanName, mbd, instanceWrapper);
// 4. 执行`raw bean`实例的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 5. 归正最终返回的`bean`实例
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (...) {
...
}
}
// ...
return exposedObject;
}
在createBean
方法中不但会执行基本的bean
实例化流程,而且还会额外添加解决循环依赖的代码块,具体执行流程如下所示:
- 执行
raw bean
实例的创建 - 通过
raw bean
实例构建工厂实例并将其添加到一级缓存 - 执行
raw bean
实例的属性填充 - 执行
raw bean
实例的初始化 - 归正最终返回的
bean
实例
其中,步骤1
、3
和4
都是bean
实例化流程的基本步骤,这在上文《源码分析:Bean
的创建》章节中已有所提及,这里就不在赘述了。而步骤2
和步骤5
则主要用于解决bean
的循环依赖,下面我们来详细看看这2
个步骤:
根据上述流程,Spring
在执行完bean
基本对象的创建后就会执行步骤2
(在属性填充和实例初始化前),即通过raw bean
实例构造出工厂实例并将其添加到一级缓存:
// 视`() -> getEarlyBeanReference(beanName, mbd, bean)`为工厂实例
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
}
}
通过这种方式,如果Spring
在属性填充阶段发生了循环依赖再次向doGetBean
方法请求当前bean
时就会触发到getSingleton(String, true)
方法的执行(此时在一级缓存中会存在当前bean
的工厂实例缓存)。在getSingleton(String, true)
方法中会一路探查到一级缓存将当前存储的工厂实例取出执行(即执行getEarlyBeanReference
方法),具体执行逻辑如下所示:
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* @param beanName the name of the bean (for error handling purposes)
* @param mbd the merged bean definition for the bean
* @param bean the raw bean instance
* @return the object to expose as bean reference
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
//...假设符合条件
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* <p>This callback gives post-processors a chance to expose a wrapper
* early - that is, before the target bean instance is fully initialized.
* The exposed object should be equivalent to the what
* {@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}
* would expose otherwise. Note that the object returned by this method will
* be used as bean reference unless the post-processor returns a different
* wrapper from said post-process callbacks. In other words: Those post-process
* callbacks may either eventually expose the same reference or alternatively
* return the raw bean instance from those subsequent callbacks (if the wrapper
* for the affected bean has been built for a call to this method already,
* it will be exposes as final bean reference by default).
* <p>The default implementation returns the given {@code bean} as-is.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @return the object to expose as bean reference
* (typically with the passed-in bean instance as default)
*/
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
}
在getEarlyBeanReference
方法中会遍历所有的SmartInstantiationAwareBeanPostProcessor
对raw bean
进行修饰处理,默认情况下SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
不会做任何处理直接原样返回bean
,而如果Spring
使用了AOP
特性则会通过其实现类中的AbstractAutoProxyCreator#getEarlyBeanReference
方法对bean
包装一层代理,具体如下所示:
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
* that wraps each eligible bean with an AOP proxy, delegating to specified interceptors
* before invoking the bean itself.
* ...
*/
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, ... {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 特别需要关注这里,在对bean包装前会将bean存入缓存中
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// ...
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
// ...
return bean;
}
}
也就是说,在默认情况下如果没有使用Spring AOP
,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
会原样返回bean
实例;而如果使用了Spring AOP
,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
则会包装一层代理层再返回。而不管在哪种情况下执行,getSingleton(String, true)
方法在通过getEarlyBeanReference
方法获得earlySingletonObject
实例后就将它添加到二级缓存中,并最终将earlySingletonObject
实例返回,完成对早期bean
的提前暴露,从而终止对bean
的再次创建、属性填充和实例初始化,避免程序在循环依赖中不断地对bean
进行实例化的困境。Spring
就是通过这种方式来解决bean
的循环依赖的,具体流程如下图所示:
+
| get A
v
+-------+--------+
+--------+ getSingleton +<-----------------------------------+
| +-------+--------+ |
| | |
| v |
| +----------+-----------+ +--------------+ +-------+ |
| | createBeanInstance +-->+ populateBean +-->+ |-| +--->
| +----------------------+ +------+-------+ +-------+ |
| | get B | get A
v v |
+-----------+-----------+ +-------+--------+ |
| getSingletonFactory | | getSingleton | |
+-----------+-----------+ +-------+--------+ |
| | |
v v |
+------------+------------+ +----------+-----------+ +------+-------+ +-------+
| getEarlyBeanReference | | createBeanInstance +-->+ populateBean +-->+ |-| +--->
+------------+------------+ +----------------------+ +------+-------+ +-------+
| ^
| |
+--------------------------------------------------------------+
一般来说,
getEarlyBeanReference
方法返回的对象与postProcessBeforeInitialization
/postProcessAfterInitialization
所返回的对象是相等的。不过,Spring
并没有特意对此作强制要求,如果我们在postProcessBeforeInitialization
/postProcessAfterInitialization
中返回与之前不一样的引用就会造成它们之间的等式不成立。
再结合上文bean
创建流程中前置处理beforeSingletonCreation
方法与后置处理afterSingletonCreation
方法的判断,不难得出Spring
就是通过这种将早期bean
提前暴露到缓存的方式来避免bean
依赖的创建再次进入到前置处理beforeSingletonCreation
方法中造成异常的抛出,具体可参考以下流程图:
|
|
v
+--------+-------+
| | circular references
+--------------+ getSingleton +<--------------------------------------------------+
| | | |
| +--------+-------+ |
| | |
| create if not exists |
| | |
| +------------------------------------+ |
| | | | |
| | v | |
| | +-------------+-------------+ | |
| | | | | |
| | | beforeSingletonCreation | | |
| | | | | |
| | +-------------+-------------+ | |
| | | | |
| | | | |
| | | | |
| | v | |
| | +--------------+---------------+ | +----------------------+ |
| | | | | | | |
| | | singletonFactory#getObject +--------->+ createBeanInstance | |
| | | | | | | |
| | +--------------+---------------+ | +----------+-----------+ |
| | | | | |
| | | | To expose earlySingletonObject |
| | | | | |
| | v | v |
| | +------------+-------------+ | +----------+------------+ |
| | | | | | | |
| | | afterSingletonCreation | | | addSingletonFactory | |
| | | | | | | |
| | +--------------------------+ | +----------+------------+ |
| | | | |
| | | | |
| +------------------------------------+ | |
| v |
| +-------+---------+ |
| | | |
+---------------------------------------------------->+ populateBean +-----------+
get from cache if exists | |
+-------+---------+
|
|continue
|
v
简单来说,Spring
就是通过将早期bean
实例earlySingletonObject
提前暴露出去的方式来解决bean
的循环依赖。这样一看感觉Spring
已经完成bean
的循环依赖的处理了,但实际上这里仅仅考虑了没有代理层(特指AbstractAutoProxyCreator
,包含Spring AOP
的处理)情况下的循环依赖,而对于循环依赖发生在需经过代理层处理(特指AbstractAutoProxyCreator
,包含Spring AOP
的处理)的bean
实例时则要加上步骤5
的配合了。
其中,createBean
方法步骤5
的执行流程如下所示:
- 通过
getSingleton(String, false)
方法获取缓存earlySingletonReference
,如果存在则继续执行第2
步,否则结束执行。 - 通过
exposedObject
属性判断bean
实例初始化前后引用是否发生过变化,如果没发生过变化则继续执行第3
步,否则结束执行。 - 将
earlySingletonReference
赋值给exposedObject
属性替换最终暴露的bean
实例。
需要注意,
getSingleton(String, false)
方法是不允许执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建。
可能对于上述步骤5
的执行流程看上去会有点迷惑,下面将结合上下文对它进一步解读:
- 对于
getSingleton(String, false)
方法,如果earlySingletonReference
为非空则表示发生过循环依赖,否则未发生过。 - 对于
initializeBean
实例初始化,如果其中初始化前置处理或者后置处理发生了类似于添加代理层的操作就会导致引用发生变化,否则不发生。
对于第
2
点中提及到在实例初始化的前置处理或者后置处理中如果将传入的实例对象变更为另一种对象也会导致引用发生变化,不过由于这种操作并非常规操作(Spring
也不推荐),在这里就不往这个方向分析了。
根据对Spring AOP
的了解,在使用Spring AOP
时会使用动态代理为bean
添加一层代理层。按照这种前提显然使用了Spring AOP
的情况是不会触发到步骤5
的,但对于没有使用Spring AOP
的场景就更让人觉得匪夷所思了,因为在这种情况下无论是通过getEarlyBeanReference
方法提前暴露的对象还是在经过实例初始化后的对象都是不变的(它们都是原始的bean
引用),这样就更没有替换的必要了。在笔者一筹莫展之际,重新阅读了一遍用于实现Spring AOP
的AbstractAutoProxyCreator
类,并且在结合用于提前暴露早期bean
的getEarlyBeanReference
方法和用于实例初始化的postProcessAfterInitialization
方法后找到了答案,下面我们先来看看这2
个方法:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, ... {
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 在raw bean进行Spring AOP前将它存入earlyProxyReferences缓存中
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 从earlyProxyReferences缓存中取出raw bean进行判断
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}
看到这里答案应该呼之欲出了,我们拿到这两个方法代入到doCreateBean
方法中就清晰明了了,即:
- 执行
createBeanInstance
方法创建raw bean
实例。 - 将第
1
步中的raw bean
实例传入addSingletonFactory
方法创建出其bean
的工厂实例并将其添加到一级缓存中。 - 执行
populateBean
方法给第1
步中的raw bean
实例填充属性。- 在执行期间发生了循环依赖再次执行
getSingleton(String, true)
方法查询出在第2
步中缓存的工厂实例,拿出该工厂实例触发getEarlyBeanReference
方法的执行。- 将第
2
步中传入的raw bean
实例添加到earlyProxyReferences
缓存。 - 将第
2
步中传入的raw bean
实例包装代理层(方法返回,结束getSingleton(String, true)
方法的执行(查询成功,结束循环依赖))。
- 将第
- 在执行期间发生了循环依赖再次执行
- 执行
initializeBean
方法给第1
步中的raw bean
实例初始化。- 在
AbstractAutoProxyCreator
后置处理器(包含Spring AOP
的处理)的postProcessAfterInitialization
方法中判断传入的bean
实例与第3
步循环依赖中触发earlyProxyReferences
缓存的bean
实例相等(都为raw bean
实例),跳过执行对传入bean
实例(raw bean
实例)的代理封装(方法返回)。
- 在
- 归正最终返回的
bean
实例- 通过
getSingleton(beanName, false)
方法获取到earlySingletonReference
实例不为空(发生过循环依赖),继续往下执行。 - 通过比较
exposedObject
实例发现没有发生过变化(因为在4
步中跳过了bean
实例的代理封装),继续往下执行。 - 将
exposedObject
属性设置为earlySingletonReference
,完成将提前暴露的代理对象归正给当前返回的exposedObject
属性,保证了循环依赖提前暴露出去的对象与初始化后返回的对象一致。
- 通过
简单来说,Spring
在循环依赖发生时将封装了代理的raw bean
提前暴露出去,然后在属性填充和初始化(不包含类似Spring AOP
等一系列会让bean
引用发生变化的处理)阶段对raw bean
内的属性等进行的赋值/修改操作会同时作用于传入bean
引用与提前暴露出去包含在代理中的bean
引用(两者为同一bean
引用),而在初始化阶段中对后置处理器AbstractAutoProxyCreator
(包含Spring AOP
的处理)的执行会对发生过bean
提前暴露的实例不做代理封装并在初始化结束后将提前暴露的代理实例赋值给最终返回的bean
实例(替换)。通过这种方式,Spring
在最大程度上保证循环依赖提前暴露的出去的对象与在初始化后返回的对象一致,这也呼应了上文提到的:“一般来说,getEarlyBeanReference
方法返回的对象与postProcessBeforeInitialization
/postProcessAfterInitialization
所返回的对象是相等的“。
不难得出,如果bean
仅通过AbstractAutoProxyCreator
(包含Spring AOP
的处理)后置处理器来生成代理层(没有覆盖非protected
的方法),无论在初始化阶段中后置处理器按照哪个顺序执行,最终都能保证提前暴露的bean
引用与初始化执行后的bean
引用一致(通过AbstractAutoProxyCreator
来添加代理层可以确保getEarlyBeanReference
方法和postProcessAfterInitialization
方法都有添加代理层的逻辑,使得最终循环依赖提前暴露的bean
引用与初始化执行后的bean
引用一致)。
没进行过
Spring AOP
的bean
实例也是会执行步骤5
(归正最终返回的bean
实例)的,但是由于提前暴露的bean
引用与初始化后的bean
引用并没有发生过变化,因此在这种情况下步骤5
是冗余处理。
但如果我们在bean
实例化的过程中添加自定义的SmartInstantiationAwareBeanPostProcessor
来生成代理层,那最终生成的bean
实例很有可能会存在一定的问题。而这其中的问题会根据自定义的SmartInstantiationAwareBeanPostProcessor
的执行顺序(在AbstractAutoProxyCreator
前后)有所不同,下面我们将根据自定义的SmartInstantiationAwareBeanPostProcessor
的执行顺序分点阐述。
首先,假设下面SmartInstantiationAwareBeanPostProcessor
类中getEarlyBeanReference
方法和postProcessAfterInitialization
方法生成代理层的逻辑是相同的,因为在不相同的情况下提前暴露的对象与初始化后所生成的对象必然不一致(无任何探讨的意义)。同时这里也得出一个结论:在实现SmartInstantiationAwareBeanPostProcessor
类时必须保证getEarlyBeanReference
方法和postProcessAfterInitialization
方法中的逻辑是相同的,以此来达到提前暴露的对象与初始化后所生成的对象是一致的。
另外,如果我们使用更顶级的
BeanPostProcessor
类来生成代理层,那么最终生成的bean
实例也极有可能是有问题的。根据上文分析,在发生循环依赖时使用getEarlyBeanReference
方法提前暴露bean
实例(含代理层)是通过SmartInstantiationAwareBeanPostProcessor
类来实现的,如果我们使用BeanPostProcessor
类来实现的话就会导致在getEarlyBeanReference
方法中暴露的对象没有经过BeanPostProcessor
来生成代理层,而在初始化后生成的对象则是经过BeanPostProcessor
来生成代理层,这显然会发生bean
实例不一致的问题。
-
当生成代理层的自定义
SmartInstantiationAwareBeanPostProcessor
在AbstractAutoProxyCreator
之前:+ | v +--------------------+-----------------------+ | | | SmartInstantiationAwareBeanPostProcessor | | | +--------------------+-----------------------+ | v +-------------+--------------+ | | | AbstractAutoProxyCreator | | | +-------------+--------------+ | v
在这种情况下,
doCreateBean
方法的执行流程就演变为下面这样了:- 执行
createBeanInstance
方法创建raw bean
实例。 - 将第
1
步中的raw bean
实例传入addSingletonFactory
方法创建出其bean
的工厂实例并将其添加到一级缓存中。 - 执行
populateBean
方法给第1
步中的raw bean
实例填充属性(发生了循环依赖)。- 在执行期间发生了循环依赖再次执行
getSingleton(String, true)
方法查询出在第2
步中缓存的工厂实例,拿出该工厂实例触发getEarlyBeanReference
方法的执行。- 在自定义
SmartInstantiationAwareBeanPostProcessor
中为传入bean
实例封装一层代理层(方法返回,进入AbstractAutoProxyCreator
继续执行)。 - 在
AbstractAutoProxyCreator
中将传入bean
实例添加到earlyProxyReferences
缓存中。 - 在
AbstractAutoProxyCreator
中将传入bean
实例封装一层代理层(方法返回,结束getSingleton(String, true)
方法的执行(查询成功,结束循环依赖))。
- 在自定义
- 在执行期间发生了循环依赖再次执行
- 执行
initializeBean
方法给第1
步中的raw bean
实例初始化。- 在
SmartInstantiationAwareBeanPostProcessor
的postProcessAfterInitialization
方法中为传入bean
实例封装一层代理层(方法返回)。 - 在
AbstractAutoProxyCreator
的postProcessAfterInitialization
方法中判断传入的bean
实例与从第2
步AbstractAutoProxyCreator#earlyProxyReferences
缓存中的bean
实例并不想等(传入的是在SmartInstantiationAwareBeanPostProcessor#postProcessAfterInitialization
中添加了代理层的bean
实例,而AbstractAutoProxyCreator#earlyProxyReferences
缓存的是第3
步中SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
添加了代理层的bean
实例,在没有重写等式判断方法和添加缓存的前提下是不相等的),所以接着为传入bean
实例的代理封装(方法返回)。
- 在
- 归正最终返回的
bean
实例- 通过
getSingleton(beanName, false)
方法获取到earlySingletonReference
实例不为空(发生过循环依赖),继续往下执行。 - 通过比较
exposedObject
实例发现发生过变化,结束执行(不替换)。
- 通过
在上述流程中我们可以看到虽然循环依赖提前暴露的对象引用与初始化后获得的对象引用是不相等的,但是它们所实现的代理层和功能点都是相同的。这在常规的场景下一般是不会产生什么大的问题的,因为在外层代理层功能相同的情况下表明了最终所经过的额外逻辑是相同的,而最终委托给被代理对象时所执行的对象也是同一单例作用域下的实例。
- 执行
-
当生成代理层的自定义
SmartInstantiationAwareBeanPostProcessor
在AbstractAutoProxyCreator
之后:+ | v +-------------+--------------+ | | | AbstractAutoProxyCreator | | | +-------------+--------------+ | v +--------------------+-----------------------+ | | | SmartInstantiationAwareBeanPostProcessor | | | +--------------------+-----------------------+ | v
在这种情况下,
doCreateBean
方法的执行流程就演变为下面这样了:- 执行
createBeanInstance
方法创建raw bean
实例。 - 将第
1
步中的raw bean
实例传入addSingletonFactory
方法创建出其bean
的工厂实例并将其添加到一级缓存中。 - 执行
populateBean
方法给第1
步中的raw bean
实例填充属性(发生了循环依赖)。- 在执行期间发生了循环依赖再次执行
getSingleton(String, true)
方法查询出在第2
步中缓存的工厂实例,拿出该工厂实例触发getEarlyBeanReference
方法的执行。- 在
AbstractAutoProxyCreator
中将传入bean
实例添加到earlyProxyReferences
缓存中。 - 在
AbstractAutoProxyCreator
中将传入bean
实例封装一层代理层(方法返回,进入SmartInstantiationAwareBeanPostProcessor
继续执行)。 - 在自定义
SmartInstantiationAwareBeanPostProcessor
中为传入bean
实例封装一层代理层(方法返回,结束getSingleton(String, true)
方法的执行(查询成功,结束循环依赖))。
- 在
- 在执行期间发生了循环依赖再次执行
- 执行
initializeBean
方法给第1
步中的raw bean
实例初始化。- 在
AbstractAutoProxyCreator
的postProcessAfterInitialization
方法中判断传入的bean
实例与从第2
步中AbstractAutoProxyCreator#earlyProxyReferences
缓存中的bean
实例相等,跳过执行对传入bean
实例的代理封装(方法返回)。 - 在
SmartInstantiationAwareBeanPostProcessor
的postProcessAfterInitialization
方法中为传入bean
实例封装一层代理层(方法返回)。
- 在
- 归正最终返回的
bean
实例- 通过
getSingleton(beanName, false)
方法获取到earlySingletonReference
实例不为空(发生过循环依赖),继续往下执行。 - 通过比较
exposedObject
实例发现发生过变化,结束执行(不替换)。
- 通过
在上述流程中我们可以看到循环依赖提前暴露的对象引用与初始化后获得的对象不但引用是不相等的,而且在功能上也是有所差异,即对循环依赖提前暴露的对象是添加了
AbstractAutoProxyCreator
和SmartInstantiationAwareBeanPostProcessor
两个代理层的,而在初始化后所得到的对象只添加了SmartInstantiationAwareBeanPostProcessor
的代理层,这种导致功能缺失的差异显然是致命的。因此,我们在开发过程中应该尽可能的保证这种执行顺序的发生。与此同时,为了避免这种情况的发生
Spring
也通过ProxyProcessorSupport
将AbstractAutoProxyCreator
的执行顺序设置到了最后。public class ProxyProcessorSupport extends ... implements Ordered, ... { /** * This should run after all other processors, so that it can just add * an advisor to existing proxies rather than double-proxy. */ private int order = Ordered.LOWEST_PRECEDENCE; @Override public int getOrder() { return this.order; } }
在开发过程中,我们应该尽量避免将自定义的
SmartInstantiationAwareBeanPostProcessor
优先级设置为最低LOWEST_PRECEDENCE
,因为两个优先级相同的类会继续根据第二维度的顺序执行。 - 执行
关于
Spring
在执行bean
实例化时为什么是将工厂实例提前暴露出去,而不是直接提前构造完earlySingletonObject
实例暴露出去呢?在笔者看来这个问题有
2
个方面的考虑:
- 从功能上讲,通过
getEarlyBeanReference
提前暴露bean
实例后可能会由于开发规范性的问题导致最终它与初始化后获得的对象不一致(具体可阅读上文)。因此Spring
只有在真正发生了循环依赖时才通过getEarlyBeanReference
提前暴露bean
实例,其他情况下则尽量避免。- 从设计上讲,通过
getEarlyBeanReference
提前暴露bean
实例会破坏了bean
实例化原有的执行流程:实例化->属性填充->实例初始化(添加AOP
代理),使得它变为:添加AOP
代理->实例化->属性填充->实例初始化,这可能会产生除了上述第1
种所产生的问题,还可能会导致其他异常的发生。因此Spring
只有在真正发生了循环依赖时才通过getEarlyBeanReference
提前暴露bean
实例,其他情况下则尽量避免(常常在迭代中会用这种手法去兼容代码,而不是直接作暴力修改处理)。这也解释了为什么
Spring
使用了三级缓存来解决循环依赖,而不是二级缓存。
最后,回到getSingleton(String, ObjectFactory)
方法在执行完createBean
方法生成bean
实例后接着执行后置处理afterSingletonCreation
将当前beanName
从singletonsCurrentlyInCreation
中移除(避免再次创建导致异常的发生),然后执行addSingleton
方法将最终完整版的bean
添加到三级缓存singletonObjects
中,同时在二级缓存earlySingletonObjects
和一级缓存singletonFactories
中将beanName
对应的缓存实例移除。
/**
* Add the given singleton object to the singleton cache of this factory.
* <p>To be called for eager registration of singletons.
* @param beanName the name of the bean
* @param singletonObject the singleton object
*/
protected void addSingleton(String beanName, Object singletonObject) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
}
至此,结束单例模式(Singleton
)下bean
的创建(含循环依赖)。
多例模式的bean
创建
相比于单例模式,多例模式的bean
创建就相对来说简单得多。首先,在多例模式下没有三级缓存的查询,而是直接通过isPrototypeCurrentlyInCreation
方法判断prototypesCurrentlyInCreation
属性是否包含当前beanName
,若包含则表示循环依赖的发生,抛出BeanCurrentlyInCreationException
异常;否则表示prototypesCurrentlyInCreation
属性不包含当前beanName
,开始执行多例模式的bean
创建。下面,我们看到具体的代码步骤:
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws ... {
String beanName = ...;
Object beanInstance;
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// ...
RootBeanDefinition mbd = ...;
// ...
// Create bean instance.
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
return adaptBeanInstance(name, beanInstance, requiredType);
}
/**
* Return whether the specified prototype bean is currently in creation
* (within the current thread).
* @param beanName the name of the bean
*/
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
与单例模式类似,在多例模式下会在bean
的创建前后加上前置处理beforePrototypeCreation
方法和后置处理afterPrototypeCreation
方法,具体代码如下所示:
/** Names of beans that are currently in creation. */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
/**
* Callback before prototype creation.
* <p>The default implementation register the prototype as currently in creation.
* @param beanName the name of the prototype about to be created
* @see #isPrototypeCurrentlyInCreation
*/
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
/**
* Callback after prototype creation.
* <p>The default implementation marks the prototype as not in creation anymore.
* @param beanName the name of the prototype that has been created
* @see #isPrototypeCurrentlyInCreation
*/
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
this.prototypesCurrentlyInCreation.remove();
}
else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}
在前置处理beforePrototypeCreation
会将当前请求的beanName
加入到prototypesCurrentlyInCreation
中;而在后置处理afterPrototypeCreation
中则将当前请求的beanName
从prototypesCurrentlyInCreation
中移除。结合isPrototypeCurrentlyInCreation
方法的判断,如果在请求多例bean
(假设依赖也是多例bean
)的过程中发生了循环依赖(在多例模式下特指具有相同beanName
的bean
)则必然会发生异常(由于没有提前暴露与缓存机制,不区分是构造器自动装配还是Setter
方法自动装配)。
根据多例模式的特性,如果在遇到对同一
beanName
的循环依赖时如果不通过异常提前终止创建流程,则会造成bean
在创建过程中不断地循环创建对象。
+
|
|
v
+----------------+-----------------+
throw ex | |
<----------+ isPrototypeCurrentlyInCreation +<----------+
| | |
+----------------+-----------------+ |
| |
| |
v |
+-------------+--------------+ |
| | |
| beforePrototypeCreation | |
| | |
+-------------+--------------+ |
| |
| |
v |
+------+-------+ |
| | |
| createBean +---------------------+
| | circular references
+------+-------+
|
|
v
+------------+-------------+
| |
| afterPrototypeCreation |
| |
+------------+-------------+
|
|
v
回归到正常流程上,如果在前置处理beforePrototypeCreation
没有发现循环依赖,那么Spring
就会进入到createBean
方法执行bean
实例的创建。
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
//...
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
// 1. 执行`raw bean`实例的创建
BeanWrapper instanceWrapper = ...createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// ...
Object exposedObject = bean;
// 2. 执行`raw bean`实例的属性填充
populateBean(beanName, mbd, instanceWrapper);
// 3. 执行`raw bean`实例的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
// ...
return exposedObject;
}
相比于单例模式,多例模式由于没有提前暴露和缓存的机制,createBean
方法的执行就简单得多,具体执行流程如下所示:
- 执行
raw bean
实例的创建 - 执行
raw bean
实例的属性填充 - 执行
raw bean
实例的初始化
在执行流程上看仅是保留了基本的raw bean
创建流程,那再结合上文前置方法beforePrototypeCreation
和后置方法afterPrototypeCreation
的处理,若在populateBean
属性填充时发生了循环依赖就会触发isPrototypeCurrentlyInCreation
的判断抛出BeanCurrentlyInCreationException
异常,具体可参考以下流程图:
+
|
|
+----------------------------------------+
| | |
| v |
| +----------------+-----------------+ |
throw ex | | | | circular references
<-----------------+ isPrototypeCurrentlyInCreation +-----------------------------------+
| | | | |
| +----------------+-----------------+ | |
| | | |
| | | |
| v | |
| +-------------+--------------+ | |
| | | | |
| | beforePrototypeCreation | | |
| | | | |
| +-------------+--------------+ | |
| | | |
| | | |
| v | |
| +------+-------+ | +----------------------+ |
| | | | | | |
| | createBean +----------------->+ createBeanInstance | |
| | | | | | |
| +------+-------+ | +----------+-----------+ |
| | | | |
| | | | |
| v | v |
| +------------+-------------+ | +-------+--------+ |
| | | | | | |
| | afterPrototypeCreation | | | populateBean +------+
| | | | | |
| +------------+-------------+ | +-------+--------+
| | | |
+----------------------------------------+ | continue
| |
v v
需要注意,上述讨论的是请求
bean
与依赖bean
都为多例模式的情况,而如果请求bean
或者依赖bean
任意一个不是多例模式(例如,单例模式)则需要结合对应作用域的bean
创建流程进行分析,由于篇幅有限就不过多展开了。
至此,结束多例模式(Prototype
)下bean
的创建(含循环依赖)。
循环依赖的结论
总的来说,如果在Spring
创建raw bean
的阶段中发生了循环依赖是无法被解决而抛出异常的,因为在这个阶段中Spring
还尚未将bean
提前暴露出去,典型的有通过构造器完成自动装配发生了循环依赖。而如果Spring
在raw bean
的属性填充阶段发生了循环依赖,则会被Spring
通过将bean
提前暴露出去的方式顺利化解掉,典型的有使用字段的自动装配和Setter
方法的自动装配来完成依赖注入。
需要注意的是,
Spring
在处理工厂方法参数的自动装配时会将它们当作是构造参数的自动装配来处理,也就是说通过工厂方法完成自动装配时发生了循环依赖也会抛出异常。
除此之外,Spring
在进行bean
的实例化过程中还是会存在一些比较隐匿比较特殊的情况会导致bean
在发生循环依赖时抛出异常。例如,在@Configuration
的配置类中存在自动装配的属性与通过实例工厂方法创建的bean
有相互依赖的关系时可能会因循环依赖抛出异常,具体如下所示:
@Configuration
public class Config {
@Autowired
private List<BaseService> serviceList;
@Bean
public CircularReferenceService circularReferenceService(){
return new CircularReferenceService(serviceList);
}
}
@Service
public class ServiceFirst implements BaseService {
@Autowired
private CircularReferenceService circularReferenceService;
}
@Service
public class ServiceSecond extends BaseService {
@Autowired
private CircularReferenceService circularReferenceService;
}
在上述场景中,如果ServiceFirst
或者ServiceSecond
先于Config
被加载就会发生循环依赖异常。要清晰解释其中发生异常的原理需要我们再次回顾上文《源码分析:Bean
的创建》章节中提到“关于通过实例工厂方法声明bean
”的实例化流程,即在通过实例工厂方法创建bean
实例前会提前获取当前实例工厂方法所在的工厂实例,具体代码如下所示:
// factoryBeanName属性指的是工厂实例bean的名称,而不是FactoryBean实例的名称
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
//...
factoryBean = this.beanFactory.getBean(factoryBeanName);
}
// ...
在这里Spring
就会使用BeanFactory#getBean
来获取工厂实例。
在有了这一层认知之后,我们结合上文关于“Bean
的创建”章节和“Bean
的循环依赖“章节的分析推演出以下执行流程:
- 执行
ServiceFirst
的bean
实例创建。 - 将早期
ServiceFirst
的bean
实例提前暴露。 - 执行
ServiceFirst#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建。 - 执行
CircularReferenceService
的创建(将其加入到了singletonsCurrentlyInCreation
属性中),由于是通过实例工厂方法声明的,所以在CircularReferenceService
创建前触发了Config
的创建(尚未将早期CircularReferenceService
的bean
实例提前暴露)。 - 执行
Config
的bean
实例创建。 - 执行
Config#serviceList
的属性填充,触发BaseService
类(含ServiceFirst
和ServiceSecond
)的创建。 - 执行
ServiceFirst
的bean
实例创建,由于在缓存中查询到被早期暴露ServiceFirst
实例(在第2
步中被暴露),所以结束ServiceFirst
的再次创建。 - 执行
ServiceSecond
的bean
实例创建。 - 执行
ServiceSecond#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建(在缓存中尚未添加CircularReferenceService
实例)。 - 执行
CircularReferenceService
的创建,由于在第4
步首次触发创建时已经将CircularReferenceService
加入到了singletonsCurrentlyInCreation
属性中,在此再次创建CircularReferenceService
时判断singletonsCurrentlyInCreation
属性中存在CircularReferenceService
,因此抛出循环依赖异常。
在上述流程中,我们可以看到由于Spring
先触发了ServiceFirst
而导致了循环依赖异常的发生;类似地,如果我们先触发了ServiceSecond
也同样会触发循环依赖异常的发生。那如果我们先触发了Config
配置类的加载是不是就不会产生问题了呢?下面我们可以来模拟一下整个流程:
- 执行
Config
的bean
实例创建。 - 将早期
Config
的bean
实例提前暴露。 - 执行
Config#serviceList
的属性填充(属性加载阶段),触发BaseService
类(含ServiceFirst
和ServiceSecond
)的创建。 - 执行
ServiceFirst
的bean
实例创建。 - 执行
ServiceFirst#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建。 - 由于
CircularReferenceService
是通过实例工厂方法声明的,所以在CircularReferenceService
创建前触发了Config
的创建。 - 执行
Config
的bean
实例创建,由于在缓存中查询到被早期暴露Config
实例(在第2
步中被暴露),所以结束Config
的再次创建。 - 执行
CircularReferenceService
的创建,将Config#serviceList
的属性传入CircularReferenceService
构造器中进行创建,但由于Config#serviceList
属性尚处于加载阶段而没有将值真正设置到属性中,所以此时传入null
到CircularReferenceService
构造器中进行构建(意味着CircularReferenceService
中的serviceList
属性为null
)。 - 完成
CircularReferenceService
的创建(含属性填充完成和实例初始化完成),并将CircularReferenceService
实例加入到缓存中。 - 完成
ServiceFirst
的bean
实例创建(含属性填充完成和实例初始化完成),并将ServiceFirst
实例加入到缓存中。 - 执行
ServiceSecond
的bean
实例创建。 - 执行
ServiceSecond#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建。 - 执行
CircularReferenceService
的bean
实例创建,由于在缓存中查询到已被实例化完成的CircularReferenceService
实例(在第9
步中将构建完成的CircularReferenceService
实例加入到缓存),所以结束CircularReferenceService
的再次创建。需要注意,在缓存中CircularReferenceService
的serviceList
属性为null
。 - 完成
ServiceSecond
的bean
实例创建(含属性填充完成和实例初始化完成),并将ServiceSecond
实例加入到缓存中。 - 执行
Config#serviceList
的属性填充(属性赋值阶段),在赋值完成后Config#serviceList
的属性值为包含ServiceFirst
实例和ServiceSecond
实例的列表。 - 完成
Config
的bean
实例创建(含属性填充完成和实例初始化完成),并将Config
实例加入到缓存中。
在上述模拟流程中,我们可以看到先触发了Config
配置类的加载同样会产生其他问题,即在属性填充前期Spring
尚未将值设置到Config#serviceList
中时触发了CircularReferenceService
的加载导致传入其构造器的Config#serviceList
属性为null
,进而使得构建完成的CircularReferenceService
实例中serviceList
属性为null
(赋值失败)。显然地,这是比循环依赖更为致命的异常,因为它会在运行时让程序发生异常而不是在启动加载时。
综上所述,一般情况下如果在Spring
创建raw bean
的阶段中发生了循环依赖是无法被解决而抛出异常的,而如果Spring
在raw bean
的属性填充阶段发生了循环依赖则会被Spring
通过将bean
提前暴露出去的方式顺利化解掉。但这并不是绝对的法则,在特殊情况下属性填充阶段发生的循环依赖也是有可能会抛出异常的(甚至会产生更为致命的异常),这取决于我们在开发中是如何使用Spring IoC
的。
至此,我们完成了整个Bean
循环依赖的分析章节。
总结
本文用了6
万多字的篇幅分别从Spring Ioc
的概念、用法和原理三个方面展开进行了相对较为全面的分析,相信读者在阅读完本文后会对Spring IoC
有一个更为全面的认识,同时也希望能帮助到读者在开发中可以规避掉一些不必要的风险。
更多详情可阅读
Spring
官方文档:
评论区