SpringBean作用域和生命週期?
前言
在 Spring 中,那些組成應用程式的主體及由 Spring IOC 容器所管理的物件,被稱之為 bean。簡單地講,bean 就是由 IOC 容器初始化、裝配及管理的物件,除此之外,bean 就與應用程式中的其他物件沒有什麼區別了。而 bean 的定義以及 bean 相互間的依賴關係將通過配置元資料來描述。
Spring中的bean預設都是單例的,這些單例Bean在多執行緒程式下如何保證執行緒安全呢?例如對於Web應用來說,Web容器對於每個使用者請求都建立一個單獨的Sevlet執行緒來處理請求,引入Spring框架之後,每個Action都是單例的,那麼對於Spring託管的單例Service Bean,如何保證其安全呢? Spring的單例是基於BeanFactory也就是Spring容器的,單例Bean在此容器內只有一個,Java的單例是基於 JVM,每個 JVM 內只有一個例項。
在大多數情況下。單例 bean 是很理想的方案。不過,有時候你可能會發現你所使用的類是易變的,它們會保持一些狀態,因此重用是不安全的。在這種情況下,將 class 宣告為單例的就不是那麼明智了。因為物件會被汙染,稍後重用的時候會出現意想不到的問題。所以 Spring 定義了多種作用域的bean。
一 bean的作用域
建立一個bean定義,其實質是用該bean定義對應的類來建立真正例項的“配方”。把bean定義看成一個配方很有意義,它與class很類似,只根據一張“處方”就可以建立多個例項。不僅可以控制注入到物件中的各種依賴和配置值,還可以控制該物件的作用域。這樣可以靈活選擇所建物件的作用域,而不必在Java Class級定義作用域。Spring Framework支援五種作用域,分別闡述如下表。

image
五種作用域中, request、session 和 global session 三種作用域僅在基於web的應用中使用(不必關心你所採用的是什麼web應用框架),只能用在基於 web 的 Spring ApplicationContext 環境。
1. singleton——唯一 bean 例項
當一個 bean 的作用域為 singleton,那麼Spring IoC容器中只會存在一個共享的 bean 例項,並且所有對 bean 的請求,只要 id 與該 bean 定義相匹配,則只會返回bean的同一例項。singleton 是單例型別(對應於單例模式),就是在建立起容器時就同時自動建立了一個bean的物件,不管你是否使用,但我們可以指定Bean節點的 lazy-init=”true”
來延遲初始化bean,這時候,只有在第一次獲取bean時才會初始化bean,即第一次請求該bean時才初始化。 每次獲取到的物件都是同一個物件。注意,singleton 作用域是Spring中的預設作用域。要在XML中將 bean 定義成 singleton ,可以這樣配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
也可以通過 @Scope
註解(它可以顯示指定bean的作用範圍。)的方式
@Service @Scope("singleton") public class ServiceImpl{ }
2. prototype——每次請求都會建立一個新的 bean 例項
當一個bean的作用域為 prototype,表示一個 bean 定義對應多個物件例項。 prototype 作用域的 bean 會導致在每次對該 bean 請求 (將其注入到另一個 bean 中,或者以程式的方式呼叫容器的 getBean() 方法 )時都會建立一個新的 bean 例項。prototype 是原型型別,它在我們建立容器的時候並沒有例項化,而是當我們獲取bean的時候才會去建立一個物件,而且我們每次獲取到的物件都不是同一個物件。根據經驗,對有狀態的 bean 應該使用 prototype 作用域,而對無狀態的 bean 則應該使用 singleton 作用域。 在 XML 中將 bean 定義成 prototype ,可以這樣配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
通過 @Scope
註解的方式實現就不做演示了。
3. request——每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效
request只適用於Web程式,每一次 HTTP 請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效,當請求結束後,該物件的生命週期即告結束。在 XML 中將 bean 定義成 request ,可以這樣配置:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
4. session——每一次HTTP請求都會產生一個新的 bean,該bean僅在當前 HTTP session 內有效
session只適用於Web程式,session 作用域表示該針對每一次 HTTP 請求都會產生一個新的 bean,同時該 bean 僅在當前 HTTP session 內有效.與request作用域一樣,可以根據需要放心的更改所建立例項的內部狀態,而別的 HTTP session 中根據 userPreferences 建立的例項,將不會看到這些特定於某個 HTTP session 的狀態變化。當HTTP session最終被廢棄的時候,在該HTTP session作用域內的bean也會被廢棄掉。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
5. globalSession
global session 作用域類似於標準的 HTTP session 作用域,不過僅僅在基於 portlet 的 web 應用中才有意義。Portlet 規範定義了全域性 Session 的概念,它被所有構成某個 portlet web 應用的各種不同的 portle t所共享。在global session 作用域中定義的 bean 被限定於全域性portlet Session的生命週期範圍內。
<bean id="user" class="com.foo.Preferences "scope="globalSession"/>
二 bean的生命週期
Spring Bean是Spring應用中最最重要的部分了。所以來看看Spring容器在初始化一個bean的時候會做那些事情,順序是怎樣的,在容器關閉的時候,又會做哪些事情。
spring版本:4.2.3.RELEASE
鑑於Spring原始碼是用gradle構建的,我也決定捨棄我大maven,嘗試下洪菊推薦過的gradle。執行beanLifeCycle模組下的junit test即可在控制檯看到如下輸出,可以清楚瞭解Spring容器在建立,初始化和銷燬Bean的時候依次做了那些事情。
Spring容器初始化 ===================================== 呼叫GiraffeService無參建構函式 GiraffeService中利用set方法設定屬性值 呼叫setBeanName:: Bean Name defined in context=giraffeService 呼叫setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader 呼叫setBeanFactory,setBeanFactory:: giraffe bean singleton=true 呼叫setEnvironment 呼叫setResourceLoader:: Resource File Name=spring-beans.xml 呼叫setApplicationEventPublisher 呼叫setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0] 執行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService 呼叫PostConstruct註解標註的方法 執行InitializingBean介面的afterPropertiesSet方法 執行配置的init-method 執行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService Spring容器初始化完畢 ===================================== 從容器中獲取Bean giraffe Name=李光洙 ===================================== 呼叫preDestroy註解標註的方法 執行DisposableBean介面的destroy方法 執行配置的destroy-method Spring容器關閉
先來看看,Spring在Bean從建立到銷燬的生命週期中可能做得事情。
initialization 和 destroy
有時我們需要在Bean屬性值set好之後和Bean銷燬之前做一些事情,比如檢查Bean中某個屬性是否被正常的設定好值了。Spring框架提供了多種方法讓我們可以在Spring Bean的生命週期中執行initialization和pre-destroy方法。
1.實現InitializingBean和DisposableBean介面
這兩個介面都只包含一個方法。通過實現InitializingBean介面的afterPropertiesSet()方法可以在Bean屬性值設定好之後做一些操作,實現DisposableBean介面的destroy()方法可以在銷燬Bean之前做一些操作。
例子如下:
public class GiraffeService implements InitializingBean,DisposableBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("執行InitializingBean介面的afterPropertiesSet方法"); } @Override public void destroy() throws Exception { System.out.println("執行DisposableBean介面的destroy方法"); } }
這種方法比較簡單,但是不建議使用。因為這樣會將Bean的實現和Spring框架耦合在一起。
2.在bean的配置檔案中指定init-method和destroy-method方法
Spring允許我們建立自己的 init 方法和 destroy 方法,只要在 Bean 的配置檔案中指定 init-method 和 destroy-method 的值就可以在 Bean 初始化時和銷燬之前執行一些操作。
例子如下:
public class GiraffeService { //通過<bean>的destroy-method屬性指定的銷燬方法 public void destroyMethod() throws Exception { System.out.println("執行配置的destroy-method"); } //通過<bean>的init-method屬性指定的初始化方法 public void initMethod() throws Exception { System.out.println("執行配置的init-method"); } }
配置檔案中的配置:
<bean name="giraffeService" class="com.giraffe.spring.service.GiraffeService" init-method="initMethod" destroy-method="destroyMethod"> </bean>
需要注意的是自定義的init-method和post-method方法可以拋異常但是不能有引數。
這種方式比較推薦,因為可以自己建立方法,無需將Bean的實現直接依賴於spring的框架。
3.使用@PostConstruct和@PreDestroy註解
除了xml配置的方式,Spring 也支援用 @PostConstruct
和 @PreDestroy
註解來指定 init
和 destroy
方法。這兩個註解均在 javax.annotation
包中。為了註解可以生效,需要在配置檔案中定義org.springframework.context.annotation.CommonAnnotationBeanPostProcessor或context:annotation-config
例子如下:
public class GiraffeService { @PostConstruct public void initPostConstruct(){ System.out.println("執行PostConstruct註解標註的方法"); } @PreDestroy public void preDestroy(){ System.out.println("執行preDestroy註解標註的方法"); } }
配置檔案:
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
實現*Aware介面 在Bean中使用Spring框架的一些物件
有些時候我們需要在 Bean 的初始化中使用 Spring 框架自身的一些物件來執行一些操作,比如獲取 ServletContext 的一些引數,獲取 ApplicaitionContext 中的 BeanDefinition 的名字,獲取 Bean 在容器中的名字等等。為了讓 Bean 可以獲取到框架自身的一些物件,Spring 提供了一組名為*Aware的介面。
這些介面均繼承於 org.springframework.beans.factory.Aware
標記介面,並提供一個將由 Bean 實現的set*方法,Spring通過基於setter的依賴注入方式使相應的物件可以被Bean使用。
網上說,這些介面是利用觀察者模式實現的,類似於servlet listeners,目前還不明白,不過這也不在本文的討論範圍內。
介紹一些重要的Aware介面:
- ApplicationContextAware : 獲得ApplicationContext物件,可以用來獲取所有Bean definition的名字。
- BeanFactoryAware :獲得BeanFactory物件,可以用來檢測Bean的作用域。
- BeanNameAware :獲得Bean在配置檔案中定義的名字。
- ResourceLoaderAware :獲得ResourceLoader物件,可以獲得classpath中某個檔案。
- ServletContextAware :在一個MVC應用中可以獲取ServletContext物件,可以讀取context中的引數。
- ServletConfigAware : 在一個MVC應用中可以獲取ServletConfig物件,可以讀取config中的引數。
public class GiraffeService implementsApplicationContextAware, ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{ @Override public void setBeanClassLoader(ClassLoader classLoader) { System.out.println("執行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName()); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("執行setBeanFactory,setBeanFactory:: giraffe bean singleton=" +beanFactory.isSingleton("giraffeService")); } @Override public void setBeanName(String s) { System.out.println("執行setBeanName:: Bean Name defined in context=" + s); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("執行setApplicationContext:: Bean Definition Names=" + Arrays.toString(applicationContext.getBeanDefinitionNames())); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { System.out.println("執行setApplicationEventPublisher"); } @Override public void setEnvironment(Environment environment) { System.out.println("執行setEnvironment"); } @Override public void setResourceLoader(ResourceLoader resourceLoader) { Resource resource = resourceLoader.getResource("classpath:spring-beans.xml"); System.out.println("執行setResourceLoader:: Resource File Name=" + resource.getFilename()); } @Override public void setImportMetadata(AnnotationMetadata annotationMetadata) { System.out.println("執行setImportMetadata"); } }
BeanPostProcessor
上面的*Aware介面是針對某個實現這些介面的Bean定製初始化的過程,
Spring同樣可以針對容器中的所有Bean,或者某些Bean定製初始化過程,只需提供一個實現BeanPostProcessor介面的類即可。 該介面中包含兩個方法,postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法會在容器中的Bean初始化之前執行, postProcessAfterInitialization方法在容器中的Bean初始化之後執行。
例子如下:
public class CustomerBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("執行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("執行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName); return bean; } }
要將BeanPostProcessor的Bean像其他Bean一樣定義在配置檔案中
<bean class="com.giraffe.spring.service.CustomerBeanPostProcessor"/>
總結
所以。。。結合第一節控制檯輸出的內容,Spring Bean的生命週期是這樣紙的:
- Bean容器找到配置檔案中 Spring Bean 的定義。
- Bean容器利用Java Reflection API建立一個Bean的例項。
- 如果涉及到一些屬性值 利用set方法設定一些屬性值。
- 如果Bean實現了BeanNameAware介面,呼叫setBeanName()方法,傳入Bean的名字。
- 如果Bean實現了BeanClassLoaderAware介面,呼叫setBeanClassLoader()方法,傳入ClassLoader物件的例項。
- 如果Bean實現了BeanFactoryAware介面,呼叫setBeanClassLoader()方法,傳入ClassLoader物件的例項。
- 與上面的類似,如果實現了其他*Aware介面,就呼叫相應的方法。
- 如果有和載入這個Bean的Spring容器相關的BeanPostProcessor物件,執行postProcessBeforeInitialization()方法
- 如果Bean實現了InitializingBean介面,執行afterPropertiesSet()方法。
- 如果Bean在配置檔案中的定義包含init-method屬性,執行指定的方法。
- 如果有和載入這個Bean的Spring容器相關的BeanPostProcessor物件,執行postProcessAfterInitialization()方法
- 當要銷燬Bean的時候,如果Bean實現了DisposableBean介面,執行destroy()方法。
- 當要銷燬Bean的時候,如果Bean在配置檔案中的定義包含destroy-method屬性,執行指定的方法。
用圖表示一下(圖來源: http://www.jianshu.com/p/d00539babca5 ):

image
與之比較類似的中文版本:

image
其實很多時候我們並不會真的去實現上面說描述的那些介面,那麼下面我們就除去那些介面,針對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的生命週期。
三 說明
本文的完成結合了下面兩篇文章,並做了相應修改:
- https://blog.csdn.net/fuzhongmin05/article/details/73389779
- https://yemengying.com/2016/07/14/spring-bean-life-cycle/
由於本文非本人獨立原創,所以未宣告為原創!在此說明!