本文是自己學習的一個總結
1、bean初始化簡介
1.1、bean的初始化發生在什麼階段,做了什麼事
當我們從xml文件或者註解中生成容器時,XML文件或者註解中描述的bean就完成了初始化。
所謂初始化,就是bean的元信息加載進容器,說具體也就是bean信息由xml文件或者註解中讀取出來,加載爲BeanDefinition,再通過BeanDefinitionRegistry將這些BeanDefiniton註冊到容器中的過程。
其中,這個初始化過程又可以分爲構造->屬性填充->初始化完成,這三個階段又分別對應着接下來的三類回調函數。
2、Bean初始化的回調函數
2.1、基於@PostConstruct,構造後回調函數
2.1.1、使用@PostConstruct
@PostConstruct,從名字上看就能知道,是bean完成初始化之後的回調註解。該註解的使用方式是在被定義成Bean的類A中實現一個方法,並用@PostContruct標註這個方法,那麼這個方法就是類A作爲Bean完成初始化之後的回調方法。類A作爲bean在容器中初始化之後,會調用被@PostContruct標註的方法。
我們看看例子。
DefaultUserFactory的實現如下,這是我們要註冊爲Bean的類。
@Component
public class DefaultUserFactory {
@PostConstruct
public void init() {
System.out.println("@PostConstruct:UserFactory 初始化");
}
}
之後在和DefaultUserFactory同一個包下定義掃描類。
@ComponentScan
public class ConfigScan {}
最後根據掃描類生成容器,我們看系統對DefaultUserFactory完成初始化後會不會調用init()函數。
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigScan.class);
最後打印結果如下,回調函數成功調用。
2.1.2、同一個類中使用多個@PostConstruct
@PostConstruct在類中使用標註方法,當這個類被初始化成bean之後就會調用@PostConstruct標註的方法。
其中,@PostConstruct可以在類中標註多個方法,並且類被初始化成bean之後,所有被@PostConstruct標註的方法都會被回調,但是調用的順序不能保證,並不是按定義順序調用的,系統似乎有自己的一套規則。
2.1.3、註解生效範圍
@PostConstruct在類A 中使用,那只有類A是通過註解的方式初始化成bean時,@PostConstruct纔會生效。
但是如果類A是通過XML初始化成bean,那@PostConstruct就不會起作用。
2.2、實現InitInitializingBean覆寫afterPropertiesSet
若類A要被註冊爲bean,那可令類A實現InitInitializingBean接口,覆寫其中的afterPropertiesSet方法。這樣A作爲bean在初始化階段中,屬性填充以後,會回調覆寫的afterPropertiesSet方法。
基於上面的代碼,我們在DefaultUserFactory中加入afterPropertiesSet相關的代碼
@Component
public class DefaultUserFactory implements UserFactory, InitializingBean {
@PostConstruct
public void init() {
System.out.println("@PostConstruct:UserFactory 初始化");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet:DefaultUserFactory初始化");
}
}
掃描類和生成容器的代碼不變。
public class ConfigScan {}
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigScan.class);
最後輸出結果如下
2.2.1、afterPropertiesSet的生效範圍
實現InitInitializingBean覆寫afterPropertiesSet,這個方法與@PostConstruct不同,無論是通過註解還是通過xml,afterPropertiesSet都可以生效。
2.3、基於@Bean的initMethod屬性,初始化後回調函數
@Bean中有個屬性是initMethod,他是用來將類中的某個方法指定爲初始化函數。我們看看例子。
接着上面的代碼,我們在DefaultUserFactory中加入@Bean相關的代碼
@Component
public class DefaultUserFactory implements UserFactory{
@PostConstruct
public void init() {
System.out.println("@PostConstruct:UserFactory 初始化");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet:DefaultUserFactory初始化");
}
@Bean(initMethod = "initFactory")
public DefaultUserFactory getDefaultUserFactory() {
return new DefaultUserFactory();
}
public void initFactory() {
System.out.println("@Bean.initMethod:DefaultUserFactory初始化");
}
}
可以預測,最後生成的容器中有兩個類型爲DefaultUserFactory的Bean。一個是基於@Component生成的bean,這個bean只設置了@PostConstruct和afterProperties的回調函數;另一個是基於@Bean生成的bean,這個類不僅有@PostConstruct和afterProperties的回調函數,還有initMethod的回調函數。
掃描類和生成容器的代碼不變。
public class ConfigScan {}
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(ConfigScan.class);
最後輸出結果如下
2.3.1、initMethod的生效範圍
因爲這也是基於註解實現的,所以只有容器是通過註解生成時initMethod纔有效,通過xml生成容器時initMethod會無效。
2.4、@PostConstruct,afterPropertiesSet和initMethod的執行順序
文章開頭說過,bean的初始化又分爲構造->屬性填充->初始化,而以上三種方式就對應着這三個階段。
@PostConstruct在構造結束後會被調用,afterPropertiesSet在屬性填充後會被調用,initMethod在初始化完成後會被調用。
3、bean的延遲初始化
3.1、bean設置延遲初始化的方式
一般來說說只有兩種,一種是xml中設置,另一種是註解中設置。
- <bean lazy-init=‘true’ ···/>
- @Lazy(true)
3.2、延遲初始化的時機和非延遲初始化的時機
延遲初始化是在容器啓動之後,需要實例化bean時纔會初始化這個bean,而非延遲初始化則是在容器啓動是就完成初始化。
我們在上面代碼的基礎上給DefaultUserFactory加上@Lazy註解
@Lazy
public class DefaultUserFactory implements InitializingBean{···}
同時在啓動容器的語句之後加上一段輸出語句表示容器啓動了,然後實例化defaultUserFactory。
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigScan.class);
System.out.println("容器啓動了");
applicationContext.getBean("defaultUserFactory");
最後輸出語句是
可以看出,defaultUserFactory是在容器啓動後,需要實施化時纔去初始化。
如果DefaultUserFactory沒有@Lazy註解,輸出語句則是下圖所示,初始化在容器啓動時。