spring載入bean例項化順序
轉載:http://blog.sina.com.cn/s/blog_525960510100ipwj.html
http://blog.sina.com.cn/s/blog_6940cab30102uwma.html
問題來源:
有一個bean為 A,一個bean為B。想要A在容器例項化的時候的一個屬性name賦值為B的一個方法funB的返回值。
如果只是在A裡單純的寫著:
private B b; private String name = b.funb();
會報錯說nullpointException,因為這個時候b還沒被set進來,所以為null。
解決辦法為如下程式碼,同時學習下spring中 InitializingBean
public class A implements InitializingBean {
private B b; private String name; // = b.funb();
public void setB(B b) { System.out.println("A.setB initialed"); this.b = b; }
public A() { System.out.println("A initialed"); }
public void init() { System.out.println("init"); this.name = b.funb(); }
@Override public String toString() { return super.toString() + this.name; }
public void afterPropertiesSet() throws Exception {
//其實放在這裡也可以
//this.name = b.funb(); System.out.println("afterPropertiesSet");
}
}
public class B {
public String funb() { System.out.println("funb"); return "B.funb"; }
public B() { System.out.println("B initialed"); } }
spring配置檔案
<beans default-> <bean id="a" class="testspring.A" init-method="init"> </bean> <bean id="b" class="testspring.B"> </bean> </beans>
測試程式碼:
public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "src/testspring/bean.xml"); A a = (A) context.getBean("a"); System.out.println(a);
}
程式輸出為:
A initialed B initialed A.setB initialed afterPropertiesSet init funb[email protected]
從這裡看到A的name屬性在bean載入完成的時候也被成功設定為B的funB方法的返回值了,要點就是用init-method來實現。
載入順序也可以看到為:
先建構函式——>然後是b的set方法注入—— >InitializingBean 的afterPropertiesSet方法——>init- method方法
總結為:
以下內容是從書中摘錄 來的,但是我發現即使摘錄一遍,對其內容的理解也會更加深入!一、Spring裝配Bean的過程 1. 例項化; 2. 設定屬性值; 3. 如果實現了BeanNameAware介面,呼叫setBeanName設定Bean的ID或者Name; 4. 如果實現BeanFactoryAware介面,呼叫setBeanFactory 設定BeanFactory; 5. 如果實現ApplicationContextAware,呼叫setApplicationContext設定ApplicationContext 6. 呼叫BeanPostProcessor的預先初始化方法; 7. 呼叫InitializingBean的afterPropertiesSet()方法; 8. 呼叫定製init-method方法; 9. 呼叫BeanPostProcessor的後初始化方法;Spring容器關閉過程 1. 呼叫DisposableBean的destroy(); 2. 呼叫定製的destroy-method方法;
==========================
spring InitializingBean init-method postConstruct 執行順序:
Spring 容器中的 Bean 是有生命週期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操作,常用的設定方式有以下三種:
通過實現 InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作方法;
通過 元素的 init-method/destroy-method屬性指定初始化之後 /銷燬之前呼叫的操作方法;
在指定方法上加上@PostConstruct 或@PreDestroy註解來制定該方法是在初始化之後還是銷燬之前呼叫。
這是我們就有個疑問,這三種方式是完全等同的嗎,孰先孰後?
下面我們將帶著這個疑問,試圖通過測試程式碼以及分析Spring原始碼找到答案。
首先,我們還是編寫一個簡單的測試程式碼:
Java程式碼 複製程式碼 收藏程式碼
public class InitSequenceBean implements InitializingBean {
public InitSequenceBean() {
System.out.println("InitSequenceBean: constructor");
}
@PostConstruct
public void postConstruct() {
System.out.println("InitSequenceBean: postConstruct");
}
public void initMethod() {
System.out.println("InitSequenceBean: init-method");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitSequenceBean: afterPropertiesSet");
}
}
並且在配置檔案中新增如下Bean定義:
好了,我們啟動Spring容器,觀察輸出結果,就可知道三者的先後順序了:
InitSequenceBean: constructor
InitSequenceBean: postConstruct
InitSequenceBean: afterPropertiesSet
InitSequenceBean: init-method
通過上述輸出結果,三者的先後順序也就一目瞭然了:
Constructor > @PostConstruct > InitializingBean > init-method
先大致分析下為什麼會出現這些的結果:構造器(Constructor)被率先呼叫毋庸置疑,InitializingBean先於init-method我們也可以理解(在也談Spring容器的生命週期中已經討論過),但是PostConstruct為何率先於InitializingBean執行呢?
我們再次帶著這個疑問去檢視Spring原始碼來一探究竟。
通過Debug並檢視呼叫棧,我們發現了這個類org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,從命名上,我們就可以得到某些資訊——這是一個BeanPostProcessor。想到了什麼?在也談Spring容器的生命週期中,我們提到過BeanPostProcessor的postProcessBeforeInitialization是在Bean生命週期中afterPropertiesSet和init-method之前執被呼叫的。
再次觀察CommonAnnotationBeanPostProcessor這個類,它繼承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顧名思義,就是在Bean初始化和銷燬的時候所作的一個前置/後置處理器。
通過檢視InitDestroyAnnotationBeanPostProcessor類下的postProcessBeforeInitialization方法:
Java程式碼 複製程式碼 收藏程式碼
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);
}
return bean;
}
檢視findLifecycleMetadata方法,繼而我們跟蹤到buildLifecycleMetadata這個方法體中,看下buildLifecycleMetadata這個方法體的內容:
Java程式碼 複製程式碼 收藏程式碼
private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {
final LifecycleMetadata newMetadata = new LifecycleMetadata();
final boolean debug = logger.isDebugEnabled();
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
if (initAnnotationType != null) {
if (method.getAnnotation(initAnnotationType) != null) {
newMetadata.addInitMethod(method);
if (debug) {
logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
}
if (destroyAnnotationType != null) {
if (method.getAnnotation(destroyAnnotationType) != null) {
newMetadata.addDestroyMethod(method);
if (debug) {
logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
}
}
});
return newMetadata;
}
分析這段程式碼發現,在這裡會去判斷某方法有沒有被initAnnotationType/destroyAnnotationType註釋,如果有,則新增到init/destroy佇列中,後續一一執行。
initAnnotationType/destroyAnnotationType註釋是什麼呢,我們在CommonAnnotationBeanPostProcessor的建構函式中看到下面這段程式碼:
Java程式碼 複製程式碼 收藏程式碼
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}
一切都清晰了吧。一言以蔽之,@PostConstruct註解後的方法在BeanPostProcessor前置處理器中就被執行了,所以當然要先於InitializingBean和init-method執行了。
最後,給出本文的結論,Bean在例項化的過程中:
Constructor > @PostConstruct > InitializingBean > init-method