前言
本文主要介紹 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