在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定義當作一個配方頗有意義,它與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的生命週期範圍內。
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的生命週期:
當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
當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的生命週期。
在學習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來完成。