Spring Bean的生命週期
衆所周知,Spring中的bean由Spring容器負責管理,包括對象的整個生命週期:創建、裝配、銷燬。
具體的生命週期通過下圖可以描述:
說明如下:
- Spring實例化一個bean,默認是單例的。
- Spring對bean進行依賴注入。
- 如果bean實現了BeanNameAware接口,則將bean的id傳給setBeanNameAware方法。
- 如果bean實現了BeanFactoryAware接口,則將beanFactory實例傳給setBeanFactory方法。
- 如果bean實現了ApplicationContextAware接口,則將應用上下文引用傳給setApplicationContext方法。
- 如果bean實現了BeanPostProcessor接口,則調用postProcessBeforeInitialization方法。
- 如果bean實現了InitializationBean接口,則調用afterPropertiesSet方法。類似的,如果bean使用了init-method屬性聲明瞭初始化方法,則該方法也會被調用。
- 如果bean實現了BeanPostProcessor接口,則調用postProcessAfterInitialization方法。
- 按照以上順序執行後,此時bean已經準備就緒,一直駐留在應用上下文中,直到被銷燬。銷燬時,如果bean實現了DisposableBean接口,將調用destory方法。同樣的,如果bean使用destory-method屬性聲明瞭銷燬方法,則該方法將會被調用。
當然,以上的這些接口只是bean可能實現的接口,一般不會同時都實現。
下面通過一個簡單的例子來驗證。
Bean:
public class LifeBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
public LifeBean(){
System.out.println("執行構造函數");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName()");
this.name = name;
}
public void init(){
System.out.println("執行init方法");
}
public void del() {
System.out.println("執行del方法");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("執行setBeanFactory方法");
}
@Override
public void setBeanName(String s) {
System.out.println("執行setBeanName方法");
}
@Override
public void destroy() throws Exception {
System.out.println("執行destory方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("執行afterProperties方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("執行setApplicationContext方法");
}
}
BeanPostProcessor子類:
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
super();
System.out.println("執行MyBeanPostProcessor構造方法");
}
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("執行postProcessBeforeInitialization方法");
return o;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("postProcessAfterInitialization方法");
return o;
}
}
在xml文件中配置:
<bean id = "life" class="howetong.cn.test.LifeBean" init-method="init" destroy-method="del"/>
<bean id = "beanPostProcessor" class="howetong.cn.test.MyBeanPostProcessor"/>
測試:
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("/spring-web.xml");
LifeBean lifeBean = (LifeBean) applicationContext.getBean("life");
System.out.println(lifeBean);
((ClassPathXmlApplicationContext)applicationContext).registerShutdownHook();
}
輸出如下:
執行MyBeanPostProcessor構造方法
執行構造函數(創建bean)
執行setBeanName方法(BeanNameAware接口)
執行setBeanFactory方法(BeanFactoryAware接口)
執行setApplicationContext方法(ApplicationContextAware接口)
執行postProcessBeforeInitialization方法(BeanPostProcessor接口)
執行afterProperties方法(InitializingBean接口)
執行init方法((init-method定義的初始化方法))
postProcessAfterInitialization方法(BeanPostProcessor接口)
howetong.cn.test.LifeBean@64bfbc86
執行destory方法(Disposable接口)
執行del方法(destroy-method定義的銷燬方法)
通過輸出結果可以看出,執行順序與上文說明的一致。
需要注意的是,BeanPostProcessor接口的作用域是整個容器,它對所有的bean都生效。容器在創建bean的過程中,會先創建實現了BeanPostProcessor接口的bean,然後在創建其他bean的時候,會將創建的每一個bean作爲參數,調用BeanPostProcessor的方法。
Spring容器
上文說,Spring中的Bean由Spring容器負責管理,那麼,什麼是Spring容器呢?從測試的例子中看到,先創建了一個ApplicationContext實例,然後從該實例中獲取了名稱爲life的bean。也就是說,ApplicationContext可以認爲是Spring的容器。繼續深入可以發現,ApplicationContext繼承了ListableBeanFactory接口,而ListableBeanFactory接口又繼承了BeanFactory接口。
事實上,BeanFactory和ApplicationContext是Spring的兩大核心接口,其中ApplicationContext是BeanFactory的子接口。它們都可以當做是Spring的容器。
Spring容器不是唯一的,而是一套的,包括根接口BeanFactory和其衆多的子類。最主要的是ApplicationContext。BeanFactory是最基本的容器,只提供了基本的DI功能。繼承了BeanFactory的ApplicationContext則功能更爲全面,能提供更多的服務,如解析配置文本信息,資源國際化等。
BeanFactory和ApplicationContext介紹
Spring Ioc容器的實現,從根源上是BeanFactory。其子類比較多,如下圖所示:
ApplicationContext是BeanFactory的一個重要的子類,它也是一個接口。ApplicationContext的常用實現類也比較多,主要包括如下幾種:
- AnnotationConfigApplicationContext
從一個或對多個基於Java的配置類中加載上下文定義,適用於Java註解的方式。 - ClassPathXMLApplicationContext
從類路徑下的一個或多個xml配置文件中加載上下文定義,適用於xml配置的方式 - FileSystemXMLApplicationContext
從文件系統下的一個或多個xml配置文件中加載上下文定義(也就是從系統盤符中加載xml配置文件)。 - AnnotationConfigWebApplicationContext
專爲web應用準備,適用於註解方式。 - XmlWebApplicationContext
從web應用下的一個或多個xml配置文件中加載上下文定義,適用於xml配置方式。
相較於BeanFactory,ApplicationContext提供了更多額外功能:
- 默認初始化所有的Singleton,也可以通過配置取消預初始化。
- 繼承MessageSource,因此支持國際化。
- 資源訪問,比如訪問URL和文件。
- 事件機制。
- 同時加載多個配置文件。
- 以聲明式方式啓動並創建Spring容器。
對Bean的初始化
BeanFactroy採用的是延遲加載形式來注入Bean的,即只有在使用到某個Bean時(調用getBean()),纔對該Bean進行加載實例化。而ApplicationContext是在容器啓動時,一次性初始化所有bean。
下面通過代碼進行驗證。以上文中的代碼爲例,創建ApplicationContext容器時,即
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-web.xml");
這一行就完成了bean的初始化工作。說明ApplicationContext確實是在容器啓動時完成bean的初始化的。
換成創建BeanFactory容器:
public static void main(String[] args) {
Resource resource=new ClassPathResource("spring-web.xml");
BeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory);
bdr.loadBeanDefinitions(resource);
// 第一次調用時,纔對bean進行初始化
// LifeBean lifeBean = (LifeBean) beanFactory.getBean("life");
// System.out.println(lifeBean);
}
啓動容器,沒有bean的初始化信息。通過getBean方法訪問life,輸出如下:
執行構造函數(創建bean)
執行setBeanName方法(BeanNameAware接口)
執行setBeanFactory方法(BeanFactoryAware接口)
執行afterProperties方法(InitializingBean接口)
執行init方法(init-method定義的初始化方法)
howetong.cn.test.LifeBean@783e6358
說明訪問時對bean進行了初始化。
比較這兩種方式,BeanFactory的啓動速度更快,但是如果有配置錯誤,則啓動時不會暴露出來,只有在訪問配置出錯的bean時纔會暴露。而ApplicationContext啓動時就會初始化所有bean,一開始就會暴露出錯誤。同時,由於ApplicationContext已經預加載了所有的bean,在訪問的時候不再需要額外的等待。
對事件的支持
ApplicationContext增加了對事件的支持。
ApplicationContext繼承了ApplicationEventPublisher接口:
public interface ApplicationEventPublisher {
void publishEvent(ApplicationEvent var1);
void publishEvent(Object var1);
}
可以通過publishEvent方法來發布容器事件。
容器事件需要繼承ApplicationEvent類:
public class EmailEvent extends ApplicationEvent {
private String address;
private String text;
public EmailEvent(Object source) {
super(source);
}
public EmailEvent(Object source, String address, String text) {
super(source);
this.address = address;
this.text = text;
}
// get,set方法
...
}
監聽器需要實現ApplicationListener接口,並且要註冊爲一個bean,由Spring進行管理。
public class EmailListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof EmailEvent) {
EmailEvent emailEvent = (EmailEvent) applicationEvent;
System.out.println("郵件地址:" + emailEvent.getAddress());
System.out.println("郵件內容:" + emailEvent.getText());
} else {
// spring內置的其他事件
System.out.println("其他事件");
}
}
}
// 配置爲bean
<bean id = "emailListener" class="howetong.cn.test.EmailListener" />
現在事件和監聽器都有了,只要讓ApplicationContext容器來發布事件消息就可以了:
public static void main(String[] args) {
ApplicationContext acx = new ClassPathXmlApplicationContext("/spring-web.xml");
EmailEvent emailEvent = new EmailEvent("test", "[email protected]", "hello world");
acx.publishEvent(emailEvent);
((AbstractApplicationContext)acx).close();
}
輸出如下:
其他事件
郵件地址:[email protected]
郵件內容:hello world
其他事件
這裏的”其他事件”是ApplicationContext容器內置的一些事件,如下圖所示:
其中,ContextRefreshEvent是容器初始化完成或刷新事件,ContextClosedEvent是容器關閉事件,ContextStartedEvent是容器調用ConfigurableApplicationContext的start方法開始/重新開始容器時的事件,ContextStoppedEvent是調用stop方法停止容器時的事件。5.x版的spring還有個RequestHandledEvent事件,當spring處理完web請求後觸發。
在上文代碼的基礎上做一點修改,監聽一下容器刷新事件和關閉事件:
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof EmailEvent) {
EmailEvent emailEvent = (EmailEvent) applicationEvent;
System.out.println("郵件地址:" + emailEvent.getAddress());
System.out.println("郵件內容:" + emailEvent.getText());
} else if (applicationEvent instanceof ContextRefreshedEvent) {
System.out.println("容器初始化完成或刷新");
} else if (applicationEvent instanceof ContextClosedEvent){
System.out.println("容器關閉");
} else {
System.out.println("其他事件");
}
}
再來執行main函數,輸出如下:
容器初始化完成或刷新
郵件地址:[email protected]
郵件內容:hello world
容器關閉
Spring容器獲取
Spring web項目啓動的時候,讀取spring配置文件來生成spring容器,也就是生成BeanFactory及其子類相關的實例。
如果要在業務代碼中獲取spring容器,以便根據id來獲取對應的bean,應該如何獲取呢?
直接注入
最簡單的方式是直接注入。
@Autowired
ApplicationContext applicationContext;
Spring初始化過程注入
上文介紹bean的生命週期時說過,如果一個bean實現了BeanFactoryAware接口,則在這個bean實例化的Aware接口檢查階段,會將spring容器BeanFactory通過setBeanFactory方法設置進來。如果bean實現了ApplicationContextAware接口,同樣會將spring容器applicationContext設置進來。據此可以想到兩種獲取spring容器的方式。
獲取BeanFactory容器
定義一個bean,實現BeanFactoryAware接口,則這個bean在項目啓動後的實例化過程中會將BeanFactory設置進來。
@Component
public class TestFactory implements BeanFactoryAware {
private static BeanFactory beanFactory;
@SuppressWarnings("AccessStaticViaInstance")
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public static BeanFactory getBeanFactory() {
return beanFactory;
}
}
通過TestFactory.getBeanFactory()即可獲取spring容器,然後調用getBean(beanId)獲取對應的bean。
獲取ApplicationContext容器
同樣定義一個bean,在spring初始化這個bean的過程中將ApplicationContext對象設置進來。
@Service
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static Object getBean(String beanId) {
if (context.containsBean(beanId)) {
return context.getBean(beanId);
}
return null;
}
public static <T> T getBeanByClass(Class<T> requiredClz) {
return context.getBean(requiredClz);
}
/**
* @return the applicationContext
*/
public static ApplicationContext getApplicationContext() {
return context;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContext.context = applicationContext;
}
}
當然,除了這兩種方式外,還有其他方式,如繼承ApplicationObjectSupport或WebApplicationObjectSupport,這裏就不詳細介紹了。