前言
本文主要介绍 Spring IoC 容器如何管理 Bean 的生命周期。
在应用开发中,常常需要执行一些特定的初始化工作,这些工作都是相对比较固定的,比如建立数据库连接,打开网络连接等,同时,在结束服务时,也有一些相对固定的销毁工作需要执行。为了便于这些工作的设计,Spring IoC 容器提供了相关的功能,可以让应用定制 Bean 的初始化和销毁过程。
Bean 生命周期
Bean 的生命周期可分为初始化阶段和销毁阶段
初始化阶段
-
启动 Spring IoC 容器
-
实例化
调用 Bean 的构造方法创建一个对象,这个对象默认是单例的。 -
设置属性
调用 Bean 对象的 setter 方法设置属性值。 -
Aware 相关的属性,注入到 Bean 对象
- 设置 Bean 名称
如果 Bean 实现了BeanNameAware
接口,调用其重写的#setBeanName(String name)
方法,将 bean 标签中的 id 或@Bean
注解的 name 属性(若没设置 name,则默认为方法名)设置为 Bean 的名称。 - 设置 Bean 工厂
如果 Bean 实现了BeanFactoryAware
接口,调用其重写的#setBeanFactory(BeanFactory factory)
方法,设置 Bean 工厂。 - 设置上下文
调用ApplicationContextAware#setApplicationContext(ApplicationContext context)
方法。
- 设置 Bean 名称
-
调用其他接口的方法,进一步初始化 Bean 对象
- 如果存在与 Bean 关联的任何
BeanPostProcessor
实现类,则调用#preProcessBeforeInitialization(Object bean, String beanName)
方法。 - 如果 Bean 实现了
InitializingBean
接口,则会调用#afterPropertiesSet()
方法。
如果为 Bean 指定了 init 方法(例如<bean />
的init-method
属性或@Bean
注解的initMethod
属性),那么将调用该方法。 - 如果存在与 Bean 关联的任何
BeanPostProcessor
实现类,则将调用#postProcessAfterInitialization(Object bean, String beanName)
方法。
- 如果存在与 Bean 关联的任何
销毁阶段
- 如果 Bean 实现了
DisposableBean
接口,当 Spring 容器关闭时,会调用#destroy()
方法。 - 如果为 Bean 指定了 destroy 方法(例如
<bean />
的destroy-method
属性或@Bean
注解的destroyMethod
属性),那么将调用该方法。
代码实例
自定义 Bean
@Data
@Slf4j
public class Person implements BeanNameAware, BeanFactoryAware, InitializingBean, ApplicationContextAware, DisposableBean {
private String name;
public Person() {
log.info("constructor: Instantiate");
}
public void setName(String name) {
log.info("setter: populate property");
this.name = name;
}
public String getName() {
log.info("getter");
return name;
}
@Override
public void setBeanName(String beanName) {
log.info("setBeanName: " + beanName);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.info("setBeanFactory");
}
@Override
public void afterPropertiesSet() {
log.info("afterPropertiesSet");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.info("setApplicationContext");
}
@Override
public void destroy() {
log.info("destroy");
}
public void customInit() {
log.info("customInit");
}
public void customDestroy() {
log.info("customDestroy");
}
}
代码中自定义了一个 Bean 类 Person
,实现以下接口并重写其中的方法:
interface | method |
---|---|
BeanNameAware | setBeanName |
BeanFactoryAware | setBeanFactory |
InitializingBean | afterPropertiesSet |
ApplicationContextAware | setApplicationContext |
DisposableBean | destroy |
另外,为 Person
加入无参构造方法、getter/setter 方法。对这些方法全部加入日志,将当前方法名打印出来,便于在控制台观察顺序。
配置 Bean
@Configuration
public class PersonConfig {
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public Person singletonPerson() {
Person singletonPerson = new Person();
singletonPerson.setName("Singleton Jake");
return singletonPerson;
}
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
@Lazy
public Person lazySingletonPerson() {
Person lazySingletonPerson= new Person();
lazySingletonPerson.setName("Lazy Singleton Jake");
return lazySingletonPerson;
}
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
@Scope("prototype")
public Person prototypePerson() {
Person prototypePerson = new Person();
prototypePerson.setName("Prototype Jake");
return prototypePerson;
}
}
上述代码为注册了三个 bean,则 IoC 容器中会存在三个类型相同,名称和作用范围不同的 bean。
classType | beanName | scope | |
---|---|---|---|
1 | Person | singletonPerson | singleton |
2 | Person | lazySingletonPerson | singleton |
3 | Person | prototypePerson | prototype |
自定义 BeanPostProcessor
@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Person) {
log.info("postProcessBeforeInitialization, beanName = " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Person) {
log.info("postProcessAfterInitialization, beanName = " + beanName);
}
return bean;
}
}
BeanPostProcessor 允许 IoC 容器在 bean 初始化前后进行处理
启动 Application
注意:想观察 IoC 容器关闭(Bean 销毁)后的打印内容,在 pom 文件中不要加上 spring-boot-starter-web
依赖。
运行 Application 类中的 main 方法,控制台的打印如下(仅截取关键部分):
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.4.RELEASE)
2020-03-02 17:41:22.111 INFO 406860 --- [ main] c.j.s.l.SpringLifecycleApplication : Starting SpringLifecycleApplication on V12CNSSZ01MGRPE with PID 406860 (C:\wengzhengkai\idea-projects\spring-lifecycle\target\classes started by 60055807 in C:\wengzhengkai\idea-projects\spring-lifecycle)
2020-03-02 17:41:22.117 INFO 406860 --- [ main] c.j.s.l.SpringLifecycleApplication : No active profile set, falling back to default profiles: default
2020-03-02 17:41:23.068 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : constructor: Instantiate
2020-03-02 17:41:23.068 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : setter: populate property
2020-03-02 17:41:23.069 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanName: singletonPerson
2020-03-02 17:41:23.069 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanFactory
2020-03-02 17:41:23.069 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : setApplicationContext
2020-03-02 17:41:23.069 INFO 406860 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessBeforeInitialization, beanName = singletonPerson
2020-03-02 17:41:23.069 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : afterPropertiesSet
2020-03-02 17:41:23.070 INFO 406860 --- [ main] com.jake.spring.lifecycle.domain.Person : customInit
2020-03-02 17:41:23.070 INFO 406860 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessAfterInitialization, beanName = singletonPerson
2020-03-02 17:41:23.201 INFO 406860 --- [ main] c.j.s.l.SpringLifecycleApplication : Started SpringLifecycleApplication in 1.921 seconds (JVM running for 3.17)
2020-03-02 17:41:23.209 INFO 406860 --- [extShutdownHook] com.jake.spring.lifecycle.domain.Person : destroy
2020-03-02 17:41:23.209 INFO 406860 --- [extShutdownHook] com.jake.spring.lifecycle.domain.Person : customDestroy
打印顺序总结如下:
-
容器启动后
Person#Person()
实例化一个对象Person#setName(String name)
设置属性 name 的值Person#setBeanName(String beanName)
设置 beanName,方法继承自BeanNameAware
接口Person#setBeanFactory(BeanFactory beanFactory)
设置 beanFactory,方法继承自BeanFactoryAware
接口Person#setApplicationContext(ApplicationContext context)
设置 applicationContext,方法继承自ApplicationContextAware
接口MyBeanPostProcessor#postProcessBeforeInitialization(Object bean, String beanName)
初始化前的后置处理,可以猜想后置的含义就是实例化和属性设置之后。Person#afterPropertiesSet()
此时属性值已经被设置完毕,方法继承自InitializingBean
接口Person#customInit()
自定义初始化方法。
-
容器关闭后
Person#destroy()
销毁 bean 之前的操作,方法继承自DisposableBean
接口Person#customDestroy()
自定义的销毁 bean 之前的操作
由打印内容 setBeanName: singletonPerson
, postProcessBeforeInitialization, beanName = singletonPerson
, postProcessAfterInitialization, beanName = singletonPerson
可知,只有 beanName 为 singletonPerson 的 Bean 才在 Spring 容器启动时被初始化。
单元测试
编写单元测试,注入ApplicationContext
对象,使用工厂方法 getBean(String beanName)
获取 PersonConfig
中注册的三种 Bean 实例。
@SpringBootTest
class SpringLifecycleApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void getSingletonBean() {
Person singletonPerson = (Person) applicationContext.getBean("singletonPerson");
assertNotNull(singletonPerson);
assertSame("Singleton Jake", singletonPerson.getName());
assertSame(singletonPerson, applicationContext.getBean("singletonPerson"));
}
@Test
void getLazySingletonBean() {
Person lazySingletonPerson = (Person) applicationContext.getBean("lazySingletonPerson");
assertNotNull(lazySingletonPerson);
assertSame("Lazy Singleton Jake", lazySingletonPerson.getName());
assertSame(lazySingletonPerson, applicationContext.getBean("lazySingletonPerson"));
}
@Test
void getPrototypeBean() {
Person prototypePerson = (Person) applicationContext.getBean("prototypePerson");
assertNotNull(prototypePerson);
assertSame("Prototype Jake", prototypePerson.getName());
assertNotSame(prototypePerson, applicationContext.getBean("prototypePerson"));
}
}
运行单元测试类,在控制台中输出结果如下:
-
容器启动部分
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.4.RELEASE) 2020-03-03 11:42:25.950 INFO 312788 --- [ main] c.j.s.l.SpringLifecycleApplicationTests : Starting SpringLifecycleApplicationTests on V12CNSSZ01MGRPE with PID 312788 (started by 60055807 in C:\wengzhengkai\idea-projects\spring-lifecycle) 2020-03-03 11:42:25.952 INFO 312788 --- [ main] c.j.s.l.SpringLifecycleApplicationTests : No active profile set, falling back to default profiles: default 2020-03-03 11:42:26.626 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : constructor: Instantiate 2020-03-03 11:42:26.627 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setter: populate property 2020-03-03 11:42:26.639 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanName: singletonPerson 2020-03-03 11:42:26.639 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanFactory 2020-03-03 11:42:26.639 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setApplicationContext 2020-03-03 11:42:26.639 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessBeforeInitialization, beanName = singletonPerson 2020-03-03 11:42:26.639 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : afterPropertiesSet 2020-03-03 11:42:26.640 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : customInit 2020-03-03 11:42:26.640 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessAfterInitialization, beanName = singletonPerson 2020-03-03 11:42:26.783 INFO 312788 --- [ main] c.j.s.l.SpringLifecycleApplicationTests : Started SpringLifecycleApplicationTests in 1.259 seconds (JVM running for 3.759)
-
getSingletonBean
2020-03-03 11:42:27.208 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : getter
-
getLazySingletonBean
2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : constructor: Instantiate 2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setter: populate property 2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanName: lazySingletonPerson 2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanFactory 2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setApplicationContext 2020-03-03 11:42:27.236 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessBeforeInitialization, beanName = lazySingletonPerson 2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : afterPropertiesSet 2020-03-03 11:42:27.236 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : customInit 2020-03-03 11:42:27.237 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessAfterInitialization, beanName = lazySingletonPerson 2020-03-03 11:42:27.237 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : getter
-
getPrototype
2020-03-03 11:42:27.227 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : constructor: Instantiate 2020-03-03 11:42:27.227 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setter: populate property 2020-03-03 11:42:27.227 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanName: prototypePerson 2020-03-03 11:42:27.227 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanFactory 2020-03-03 11:42:27.227 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setApplicationContext 2020-03-03 11:42:27.228 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessBeforeInitialization, beanName = prototypePerson 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : afterPropertiesSet 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : customInit 2020-03-03 11:42:27.228 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessAfterInitialization, beanName = prototypePerson 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : getter 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : constructor: Instantiate 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setter: populate property 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanName: prototypePerson 2020-03-03 11:42:27.228 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setBeanFactory 2020-03-03 11:42:27.229 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : setApplicationContext 2020-03-03 11:42:27.229 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessBeforeInitialization, beanName = prototypePerson 2020-03-03 11:42:27.229 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : afterPropertiesSet 2020-03-03 11:42:27.229 INFO 312788 --- [ main] com.jake.spring.lifecycle.domain.Person : customInit 2020-03-03 11:42:27.229 INFO 312788 --- [ main] c.j.s.l.processor.MyBeanPostProcessor : postProcessAfterInitialization, beanName = prototypePerson
由单元测试的结果可知:
- 运行
@SpringBootTest
注解的单元测试类和直接运行 Application 在容器启动部分是一致的,都是默认初始化单例 Bean。 @Lazy
或@Scope("prototype")
注解的@Bean
配置,都是在被ApplicationContext#getBean(String beanName)
调用时才进行初始化。@Lazy
注解的 Bean,仅仅做一次初始化,获取的单例,仅仅是延迟初始化而已。@Scope("prototype")
注解的 Bean,基于原型模式,其背后原理是拷贝,每次都进行初始化。
参考博客
- Spring Bean 生命周期 (实例结合源码彻底讲透)
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContextAware.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/InitializingBean.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/DisposableBean.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
- https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html