Spring Bean的生命週期和Spring容器

Spring Bean的生命週期

衆所周知,Spring中的bean由Spring容器負責管理,包括對象的整個生命週期:創建、裝配、銷燬。
具體的生命週期通過下圖可以描述:
在這裏插入圖片描述
說明如下:

  1. Spring實例化一個bean,默認是單例的。
  2. Spring對bean進行依賴注入。
  3. 如果bean實現了BeanNameAware接口,則將bean的id傳給setBeanNameAware方法。
  4. 如果bean實現了BeanFactoryAware接口,則將beanFactory實例傳給setBeanFactory方法。
  5. 如果bean實現了ApplicationContextAware接口,則將應用上下文引用傳給setApplicationContext方法。
  6. 如果bean實現了BeanPostProcessor接口,則調用postProcessBeforeInitialization方法。
  7. 如果bean實現了InitializationBean接口,則調用afterPropertiesSet方法。類似的,如果bean使用了init-method屬性聲明瞭初始化方法,則該方法也會被調用。
  8. 如果bean實現了BeanPostProcessor接口,則調用postProcessAfterInitialization方法。
  9. 按照以上順序執行後,此時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,這裏就不詳細介紹了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章