由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp...
通过上述步骤即把配置读取到容器中,然后容器会再进一步生成相应的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
实例。
xml
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
通过Java
&&构造器配置实例
在Java
配置中,通过@Configuration
和@Component
注解及其衍生注解(典型的有: @Controller
、@Service
、@Repository
)声明的类会被当作bean
注册到容器中。对于这种两种方式,IoC
也会使用它们的构造器来创建bean
实例。
java
@Configuration
public class MyConfig {}
@Configuration
一般用于标识bean
定义的来源类。
java
@Component
public class MyComponent {}
@Component
一般用于标识Spring
的通用组件类。为了起到区分作用,Spring
通过元注解组合@Component
的方式衍生出三个更专用的注解,分别是持久层的@Repository
、服务层的@Service
和表示层的@Controller
。通过这种方式的区分,我们可以对业务层级进行更针对性地处理,例如对不同层级作增强处理(AOP
)。另外,官方可能也会在未来的发行版中对相应的注解添加额外的语义(例如,对于@Repository
注解提供持久层异常自动转换的支持(已支持))。
通过工厂方法实例化
通过XML
&&工厂方法配置实例
在XML
配置中,我们可以在<bean>
标签中设置factory-method
属性来指定创建bean
实例的工厂方法,其中工厂方法又分为静态工厂方法和实例工厂方法。
-
静态工厂方法
对于静态工厂方法,我们需要在
<bean>
标签中设置class
属性来指定工厂方法的所在类,并通过factory-method
属性来指定工厂方法。xml<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
javapublic 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
实例。xml<!-- 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"/>
javapublic class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } }
通过这种方式,我们也可以在一个工厂类中声明多个实例工厂方法,如下例所示:
xml<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"/>
javapublic 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
实例都是通过实例工厂方法来完成的。java@Configuration public class MyConfig { @Bean public MyService myService() { return new MyService(); } }
java@Component public class MyComponent { @Bean public MyService myService() { return new MyService(); } }
-
静态工厂方法
与实例工厂方法类似,
Spring
会把@Configuration
和@Component
注解类中的标识了@Bean
的静态方法当作是静态工厂方法来处理。即,使用这种方式创建的bean
实例都是通过静态工厂方法来完成的。java@Configuration public class MyConfig { @Bean public static MyService myService() { return new MyService(); } }
java@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
。
java
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
方法注入。
通过构造器注入依赖
对于构造器注入,我们首先需要将相应的依赖声明为构造器参数,即:
java
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>
来标记构造参数及需要被注入的依赖。
xml
<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
来标记需要使用依赖注入的构造器。
java
@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
注解标记的方法)则只需要在方法参数上指定需要注入的依赖即可。
java
@Configuration
public class MyConfig {
@Bean
public SimpleBean simpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) {
return new SimpleBean(oneInjectBean, twoInjectBean);
}
}
更多详情可阅读:
通过Setter
注入依赖
对于Setter
方法注入,我们首先需要在bean
中定义对应依赖的Setter
方法,即:
java
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
方法完成依赖注入。
xml
<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
方法的依赖注入。
java
@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
的自动装配模式,例如:javapublic 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
模式,则可以像如下这样配置:xml<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
模式,则可以像如下这样配置:xml<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
模式,则可以像如下这样配置:xml<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
模式进行自动装配。如果我们以构造器的方式配置自动装配,则可以像如下这样配置:
java@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Autowired public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
如果我们以
Setter
方法的方式自动装配,则可以像如下这样配置:java@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; } }
如果我们以属性的方式自动装配,则可以像如下这样配置:
java@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
,即:xml<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
属性要高的。xml<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
,即:java@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
来控制是否延长初始化。xml<beans> <!-- 通过指定lazy-init属性为true即可让SimpleBean不会在容器启动时进行实例化 --> <bean id="simpleBean" class="com.example.SimpleBean" lazy-init="true"/> </beans>
另外,我们还可以通过在
<beans/>
元素上使用default-lazy-init
属性来让它所包含的<bean>
都实现延迟初始化。xml<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
-
通过
Java
的方式配置延迟初始化在
Java
配置中,我们可以通过@Lazy
注解来达到与lazy-init
同样的效果(即延迟初始化)。其中,@Lazy
存在以下几种不同的声明方式:通过将
@Lazy
注解声明在@Component
及其衍生类(@Controller
、@Service
或@Repository
)上以让它实现延迟初始化。java@Lazy @Component public class SimpleBean { }
通过将
@Lazy
注解声明在@Bean
方法上以让它实现延迟初始化。java@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)
(显式设置为不延迟初始化)来覆盖类上指定的默认行为(延迟初始化)。java@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
代理,在依赖被调用时才会去检索或创建依赖实例(这可能会由于依赖不存在而抛出异常)。java@Component public class SimpleBean { @Lazy @Autowired private OneInjectBean oneInjectBean; @Lazy @Autowired private TwoInjectBean twoInjectBean; }
java@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Lazy @Autowired public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
java@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
来代替,即将它们作为注入点。java@Component public class SimpleBean { @Autowired private ObjectProvider<OneInjectBean> oneInjectBean; @Autowired private ObjectProvider<TwoInjectBean> twoInjectBean; }
java@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; } }
java@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
名称来显式指定它们依赖关系的,以此强制它们的初始化顺序。xml<beans> <bean id="simpleBean1" class="com.example.SimpleBean1" depends-on="simpleBean2"/> <bean id="simpleBean2" class="com.example.SimpleBean2"/> </beans>
如果
bean
对多个bean
存在依赖,则可以通过分隔符(逗号、空格和分号都是有效的分隔符)来间隔多个bean
名称。xml<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
属性的用法相同。java@DependsOn(value={"simpleBean2","simpleBean3"}) @Component public class SimpleBean1 { } @Component public class SimpleBean2 { } @Component public class SimpleBean3 { }
java@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
的循环依赖,例如:
java
@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
中(经典的鸡和蛋场景),具体用法如下:
java
@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
属性来指定其作用域。xml<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
。java// 默认为singleton @Scope @Component public class SimpleBean { }
java@Scope(scopeName="singleton") @Component public class SimpleBean { }
java@Scope(scopeName="prototype") @Component public class SimpleBean { }
java// 等价于@Scope(scopeName="request") @RequestScope @Component public class SimpleBean { }
java// 等价于@Scope(scopeName="session") @SessionScope @Component public class SimpleBean { }
java// 等价于@Scope(scopeName="application") @ApplicationScope @Component public class SimpleBean { }
更多详情可阅读一下资料:
作用域的依赖
由于作用域的不同,IoC
容器中bean
存活的生命周期也有所差别。如果需要将生命周期较短的bean
注入到生命周期更长的bean
,直接的依赖注入可能会产生问题。对于这种情况,我们可以使用AOP
代理来代替不同作用域的bean
,即我们需要注入的是一个(与作用域对象具有相同接口的)代理对象,它会从对应的作用域中检索出真正的目标实例,并将方法的调用委托给它。
-
通过
XML
方式配置注入代理在
XML
配置中,我们可以在<bean>
中加入子标签<aop:scoped proxy/>
以表示对该bean
生成代理对象。xml<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
方法,它们是不会被委托给目标实例的。
xml<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
。xml<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
动态代理创建代理类。java@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
。java@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
能在特定的生命周期上执行特定的操作。
java
@Component
public class SimpleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
IoC
容器会在设置完所有必要的属性后调用org.springframework.beans.factory.InitializingBean
接口类(如有)来指定初始化工作,其中InitializingBean
接口只有一个方法,即:
javavoid afterPropertiesSet() throws Exception;
java
@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
接口只有一个方法,即:
javavoid 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
属性分别指定初始化方法和销毁方法。xml<bean id="simpleBean" class="com.example.SimpleBean" init-method="init" destroy-method="cleanup"/>
javapublic 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
属性分别指定初始化方法和销毁方法。java@Configuration public class MyConfig { @Bean(initMethod="init", destroyMethod="cleanup") public SimpleBean simpleBean() { return new SimpleBean(); } }
javapublic 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
注解分别指定初始化方法和销毁方法。java@Configuration public class MyConfig { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } }
java// 直接用@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>
的初始化方法和销毁方法。
xml<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
接口(可用于启动和停止某些进程)。
java
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
在Spring
中,任何实现了Lifecycle
接口的(被Spring
管理的)对象都会在ApplicationContext
(IoC
容器)收到启动和停止信号时被级联触发。在实现上,它是通过LifecycleProcessor
来实现这一点:
java
/**
* LifecycleProcessor继承了Lifecycle接口,并且添加了两种额外的方法,用于响应刷新和关闭的上下文。
*/
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
需要注意
Spring
并没有保证在bean
销毁前一定会有停止通知发出,一般来说Lifecycle
实现会在接收到销毁方法(destruction
)的回调前先收到停止的通知(Lifecycle#stop
),但是对于在上下文生命周期内的热更新或者在停止刷新的间隔内它们只会收到销毁方法(destruction
)的回调。
虽然Lifecycle
接口定义了用于启动和停止的方法,但是这并不意味着它会在上下文刷新时自动启动。如果需要更细粒度地控制自动启动和特定bean
的优雅停止,可以考虑继承SmartLifecycle
接口。
java
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
SmartLifecycle
除了扩展Lifecycle
外,还增加了Phased
的扩展。对于两个存在依赖关系的对象它们启动和停止的顺序是十分重要的,比如依赖方在其依赖之后开始,并在其依赖之前停止。但是,在开发过程中我们并不一定能知道两个具体对象的直接依赖关系,而是只知道某种类型的对象应该在另一种类型的对象之前开始,对于这种情况我们就需要用到Phased
的扩展了:
javapublic 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
来覆盖默认的生命周期处理器实例。而如果要修改超时时间,添加以下配置即可:
xml<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
实现已经存在相关代码,所以无需额外配置)。
javaimport 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
的实例引用给我们。javapublic 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
实例的名称给我们。javapublic 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
的加载。javaApplicationContext classPathContext = new ClassPathXmlApplicationContext("beans.xml"); MyService service = classPathContext.getBean("myService", MyService.class);
javaApplicationContext fileSystemContext = new FileSystemXmlApplicationContext("resources/beans.xml"); MyService service = fileSystemContext.getBean("myService", MyService.class);
-
通过
Java
配置的方式对于
Java
配置的方式,我们可以将@Configuration
、@Component
等配置类设置到AnnotationConfigApplicationContext
中进行bean
的加载。javaApplicationContext 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
的注册。
java
@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
配置文件在这里就不展开讨论了,有兴趣的读者可进一步阅读相关资料进行了解。
java
/**
* 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
容器中。java@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
的注册和加载。java/** * 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
方法中。java/** * 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
注解所修饰的类的元信息。
更多详情可阅读一下资料: