Spring中關於bean的做用域與生命週期

在Spring中,那些組成應用程序的主體及由Spring IoC容器所管理的對象,被稱之爲bean。簡單地講,bean就是由IoC容器初始化、裝配及管理的對象,除此以外,bean就與應用程序中的其餘對象沒有什麼區別了。而bean的定義以及bean相互間的依賴關係將經過配置元數據來描述。web

  Spring中的bean默認都是單例的,這些單例Bean在多線程程序下如何保證線程安全呢?例如對於Web應用來講,Web容器對於每一個用戶請求都建立一個單獨的Sevlet線程來處理請求,引入Spring框架以後,每一個Action都是單例的,那麼對於Spring託管的單例Service Bean,如何保證其安全呢? Spring的單例是基於BeanFactory也就是Spring容器的,單例Bean在此容器內只有一個,Java的單例是基於JVM,每一個JVM內只有一個實例。spring

一、bean的做用域

  建立一個bean定義,其實質是用該bean定義對應的類來建立真正實例的「配方」。把bean定義當作一個配方頗有意義,它與class很相似,只根據一張「處方」就能夠建立多個實例。不只能夠控制注入到對象中的各類依賴和配置值,還能夠控制該對象的做用域。這樣能夠靈活選擇所建對象的做用域,而沒必要在Java Class級定義做用域。Spring Framework支持五種做用域,分別闡述以下表。安全

  五種做用域中,request、session和global session三種做用域僅在基於web的應用中使用(沒必要關心你所採用的是什麼web應用框架),只能用在基於web的Spring ApplicationContext環境。session

  (1)當一個bean的做用域爲Singleton,那麼Spring IoC容器中只會存在一個共享的bean實例,而且全部對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。Singleton是單例類型,就是在建立起容器時就同時自動建立了一個bean的對象,無論你是否使用,他都存在了,每次獲取到的對象都是同一個對象。注意,Singleton做用域是Spring中的缺省做用域。要在XML中將bean定義成singleton,能夠這樣配置:多線程

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

 

  (2)當一個bean的做用域爲Prototype,表示一個bean定義對應多個對象實例。Prototype做用域的bean會致使在每次對該bean請求(將其注入到另外一個bean中,或者以程序的方式調用容器的getBean()方法)時都會建立一個新的bean實例。Prototype是原型類型,它在咱們建立容器的時候並無實例化,而是當咱們獲取bean的時候纔會去建立一個對象,並且咱們每次獲取到的對象都不是同一個對象。根據經驗,對有狀態的bean應該使用prototype做用域,而對無狀態的bean則應該使用singleton做用域。在XML中將bean定義成prototype,能夠這樣配置:app

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
 或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

 

  (3)當一個bean的做用域爲Request,表示在一次HTTP請求中,一個bean定義對應一個實例;即每一個HTTP請求都會有各自的bean實例,它們依據某個bean定義建立而成。該做用域僅在基於web的Spring ApplicationContext情形下有效。考慮下面bean定義:框架

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

 

  針對每次HTTP請求,Spring容器會根據loginAction bean的定義建立一個全新的LoginAction bean實例,且該loginAction bean實例僅在當前HTTP request內有效,所以能夠根據須要放心的更改所建實例的內部狀態,而其餘請求中根據loginAction bean定義建立的實例,將不會看到這些特定於某個請求的狀態變化。當處理請求結束,request做用域的bean實例將被銷燬。ide

  (4)當一個bean的做用域爲Session,表示在一個HTTP Session中,一個bean定義對應一個實例。該做用域僅在基於web的Spring ApplicationContext情形下有效。考慮下面bean定義:函數

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

 

  針對某個HTTP Session,Spring容器會根據userPreferences bean定義建立一個全新的userPreferences bean實例,且該userPreferences bean僅在當前HTTP Session內有效。與request做用域同樣,能夠根據須要放心的更改所建立實例的內部狀態,而別的HTTP Session中根據userPreferences建立的實例,將不會看到這些特定於某個HTTP Session的狀態變化。當HTTP Session最終被廢棄的時候,在該HTTP Session做用域內的bean也會被廢棄掉。工具

  (5)當一個bean的做用域爲Global Session,表示在一個全局的HTTP Session中,一個bean定義對應一個實例。典型狀況下,僅在使用portlet context的時候有效。該做用域僅在基於web的Spring ApplicationContext情形下有效。考慮下面bean定義:

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>

 

  global session做用域相似於標準的HTTP Session做用域,不過僅僅在基於portlet的web應用中才有意義。Portlet規範定義了全局Session的概念,它被全部構成某個portlet web應用的各類不一樣的portlet所共享。在global session做用域中定義的bean被限定於全局portlet Session的生命週期範圍內。

二、bean的生命週期

  Spring中bean的實例化過程(很差意思,我盜圖了):

  與上圖相似,bean的生命週期流程圖:

  Bean實例生命週期的執行過程以下:

  • Spring對bean進行實例化,默認bean是單例;

  • Spring對bean進行依賴注入;

  • 若是bean實現了BeanNameAware接口,spring將bean的id傳給setBeanName()方法;

  • 若是bean實現了BeanFactoryAware接口,spring將調用setBeanFactory方法,將BeanFactory實例傳進來;

  • 若是bean實現了ApplicationContextAware接口,它的setApplicationContext()方法將被調用,將應用上下文的引用傳入到bean中;

  • 若是bean實現了BeanPostProcessor接口,它的postProcessBeforeInitialization方法將被調用;

  • 若是bean實現了InitializingBean接口,spring將調用它的afterPropertiesSet接口方法,相似的若是bean使用了init-method屬性聲明瞭初始化方法,該方法也會被調用;

  • 若是bean實現了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法將被調用;

  • 此時bean已經準備就緒,能夠被應用程序使用了,他們將一直駐留在應用上下文中,直到該應用上下文被銷燬;

  • 若bean實現了DisposableBean接口,spring將調用它的distroy()接口方法。一樣的,若是bean使用了destroy-method屬性聲明瞭銷燬方法,則該方法被調用;

  其實不少時候咱們並不會真的去實現上面說描述的那些接口,那麼下面咱們就除去那些接口,針對bean的單例和非單例來描述下bean的生命週期:

2.1 單例管理的對象

  當scope=」singleton」,即默認狀況下,會在啓動容器時(即實例化容器時)時實例化。但咱們能夠指定Bean節點的lazy-init=」true」來延遲初始化bean,這時候,只有在第一次獲取bean時纔會初始化bean,即第一次請求該bean時才初始化。以下配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>

 

  若是想對全部的默認單例bean都應用延遲初始化,能夠在根節點beans設置default-lazy-init屬性爲true,以下所示:

<beans default-lazy-init="true" …>

 

  默認狀況下,Spring在讀取xml文件的時候,就會建立對象。在建立對象的時候先調用構造器,而後調用init-method屬性值中所指定的方法。對象在被銷燬的時候,會調用destroy-method屬性值中所指定的方法(例如調用Container.destroy()方法的時候)。寫一個測試類,代碼以下:

public class LifeBean {
    private String name;  

    public LifeBean(){  
        System.out.println("LifeBean()構造函數");  
    }  
    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        System.out.println("setName()");  
        this.name = name;  
    }  

    public void init(){  
        System.out.println("this is init of lifeBean");  
    }  

    public void destory(){  
        System.out.println("this is destory of lifeBean " + this);  
    }  
}

  life.xml配置以下:

<bean id="life_singleton" class="com.bean.LifeBean" scope="singleton" 
            init-method="init" destroy-method="destory" lazy-init="true"/>

  測試代碼以下:

public class LifeTest {
    @Test 
    public void test() {
        AbstractApplicationContext container = 
        new ClassPathXmlApplicationContext("life.xml");
        LifeBean life1 = (LifeBean)container.getBean("life");
        System.out.println(life1);
        container.close();
    }
}

 

  運行結果以下:

LifeBean()構造函數
this is init of lifeBean
com.bean.LifeBean@573f2bb1
……
this is destory of lifeBean com.bean.LifeBean@573f2bb1

2.2 非單例管理的對象

  當scope=」prototype」時,容器也會延遲初始化bean,Spring讀取xml文件的時候,並不會馬上建立對象,而是在第一次請求該bean時才初始化(如調用getBean方法時)。在第一次請求每個prototype的bean時,Spring容器都會調用其構造器建立這個對象,而後調用init-method屬性值中所指定的方法。對象銷燬的時候,Spring容器不會幫咱們調用任何方法,由於是非單例,這個類型的對象有不少個,Spring容器一旦把這個對象交給你以後,就再也不管理這個對象了。

  爲了測試prototype bean的生命週期life.xml配置以下:

 

<bean id="life_prototype" class="com.bean.LifeBean" scope="prototype" init-method="init" destroy-method="destory"/>

 

  測試程序以下:

public class LifeTest {
    @Test 
    public void test() {
        AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml");
        LifeBean life1 = (LifeBean)container.getBean("life_singleton");
        System.out.println(life1);

        LifeBean life3 = (LifeBean)container.getBean("life_prototype");
        System.out.println(life3);
        container.close();
    }
}

  運行結果以下:

LifeBean()構造函數
this is init of lifeBean
com.bean.LifeBean@573f2bb1
LifeBean()構造函數
this is init of lifeBean
com.bean.LifeBean@5ae9a829
……
this is destory of lifeBean com.bean.LifeBean@573f2bb1

  能夠發現,對於做用域爲prototype的bean,其destroy方法並無被調用。若是bean的scope設爲prototype時,當容器關閉時,destroy方法不會被調用。對於prototype做用域的bean,有一點很是重要,那就是Spring不能對一個prototype bean的整個生命週期負責:容器在初始化、配置、裝飾或者是裝配完一個prototype實例後,將它交給客戶端,隨後就對該prototype實例漠不關心了。無論何種做用域,容器都會調用全部對象的初始化生命週期回調方法。但對prototype而言,任何配置好的析構生命週期回調方法都將不會被調用。清除prototype做用域的對象並釋聽任何prototype bean所持有的昂貴資源,都是客戶端代碼的職責(讓Spring容器釋放被prototype做用域bean佔用資源的一種可行方式是,經過使用bean的後置處理器,該處理器持有要被清除的bean的引用)。談及prototype做用域的bean時,在某些方面你能夠將Spring容器的角色看做是Java new操做的替代者,任何遲於該時間點的生命週期事宜都得交由客戶端來處理。

  Spring容器能夠管理singleton做用域下bean的生命週期,在此做用域下,Spring可以精確地知道bean什麼時候被建立,什麼時候初始化完成,以及什麼時候被銷燬。而對於prototype做用域的bean,Spring只負責建立,當容器建立了bean的實例後,bean的實例就交給了客戶端的代碼管理,Spring容器將再也不跟蹤其生命週期,而且不會管理那些被配置成prototype做用域的bean的生命週期。

2.3 引伸

  在學習Spring IoC過程當中發現,每次產生ApplicationContext工廠的方式是:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

 

  這樣產生ApplicationContext就有一個弊端,每次訪問加載bean的時候都會產生這個工廠,因此這裏須要解決這個問題。

  ApplicationContext是一個接口,它繼承自BeanFactory接口,除了包含BeanFactory的全部功能以外,在國際化支持、資源訪問(如URL和文件)、事件傳播等方面進行了良好的支持。

  解決問題的方法很簡單,在web容器啓動的時候將ApplicationContext轉移到ServletContext中,由於在web應用中全部的Servlet都共享一個ServletContext對象。那麼咱們就能夠利用ServletContextListener去監聽ServletContext事件,當web應用啓動的是時候,咱們就將ApplicationContext裝載到ServletContext中。 Spring容器底層已經爲咱們想到了這一點,在spring-web-xxx-release.jar包中有一個已經實現了ServletContextListener接口的類ContextLoader,其源碼以下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    private ContextLoader contextLoader;

    public ContextLoaderListener() {

    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

    @Deprecated
    protected ContextLoader createContextLoader() {
        return null;
    }

    @Deprecated
    public ContextLoader getContextLoader() {
        return this.contextLoader;
    }

    public void contextDestroyed(ServletContextEvent event) {
        if (this.contextLoader != null) {
        this.contextLoader.closeWebApplicationContext(event.getServletContext());
        }
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

  這裏就監聽到了servletContext的建立過程, 那麼 這個類又是如何將applicationContext裝入到serveletContext容器中的呢?

  this.contextLoader.initWebApplicationContext(event.getServletContext())方法的具體實現中:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
         throw new IllegalStateException(
                 "Cannot initialize context because there is already a root application context present - " +
                 "check whether you have multiple ContextLoader* definitions in your web.xml!");
     }

     Log logger = LogFactory.getLog(ContextLoader.class);
     servletContext.log("Initializing Spring root WebApplicationContext");
     if (logger.isInfoEnabled()) {
         logger.info("Root WebApplicationContext: initialization started");
     }
     long startTime = System.currentTimeMillis();

     try {
          // Store context in local instance variable, to guarantee that
          // it is available on ServletContext shutdown.
         if (this.context == null) {
             this.context = createWebApplicationContext(servletContext);
         }
         if (this.context instanceof ConfigurableWebApplicationContext) {
             ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
             if (!cwac.isActive()) {
                 // The context has not yet been refreshed -> provide services such as
                 // setting the parent context, setting the application context id, etc
                 if (cwac.getParent() == null) {
                     // The context instance was injected without an explicit parent ->
                     // determine parent for root web application context, if any.
                     ApplicationContext parent = loadParentContext(servletContext);
                     cwac.setParent(parent);
                 }
                 configureAndRefreshWebApplicationContext(cwac, servletContext);
             }
         }
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

         ClassLoader ccl = Thread.currentThread().getContextClassLoader();
         if (ccl == ContextLoader.class.getClassLoader()) {
             currentContext = this.context;
         }
         else if (ccl != null) {
             currentContextPerThread.put(ccl, this.context);
         }

         if (logger.isDebugEnabled()) {
             logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                     WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
         }
         if (logger.isInfoEnabled()) {
             long elapsedTime = System.currentTimeMillis() - startTime;
             logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
         }

         return this.context;
     }
     catch (RuntimeException ex) {
         logger.error("Context initialization failed", ex);
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
         throw ex;
     }
     catch (Error err) {
         logger.error("Context initialization failed", err);
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
         throw err;
     }
 }

  這裏的重點是servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context),用key:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE value: this.context的形式將applicationContext裝載到servletContext中了。另外從上面的一些註釋咱們能夠看出: WEB-INF/applicationContext.xml, 若是咱們項目中的配置文件不是這麼一個路徑的話 那麼咱們使用ContextLoaderListener 就會出問題, 因此咱們還須要在web.xml中配置咱們的applicationContext.xml配置文件的路徑。

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

  剩下的就是在項目中開始使用 servletContext中裝載的applicationContext對象了: 那麼這裏又有一個問題,裝載時的key是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,咱們在代碼中真的要使用這個嗎? 其實Spring爲咱們提供了一個工具類WebApplicationContextUtils,接着咱們先看下如何使用,而後再去看下這個工具類的源碼:

WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());

 

  接着來看下這個工具類的源碼:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

  這裏就能很直觀清晰地看到 經過key值直接獲取到裝載到servletContext中的 applicationContext對象了。

  ContextLoaderListener監聽器的做用就是啓動Web容器時,自動裝配ApplicationContext的配置信息,由於它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啓動容器時,就會默認執行它實現的方法。在ContextLoaderListener中關聯了ContextLoader這個類,整個加載配置過程由ContextLoader來完成。

相關文章
相關標籤/搜索