SpringBean詳解 一點課堂(多岸學院)

前言

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

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

在大多數狀況下。單例 bean 是很理想的方案。不過,有時候你可能會發現你所使用的類是易變的,它們會保持一些狀態,所以重用是不安全的。在這種狀況下,將 class 聲明爲單例的就不是那麼明智了。由於對象會被污染,稍後重用的時候會出現意想不到的問題。因此 Spring 定義了多種做用域的bean。spring

一 bean的做用域

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

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

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的做用範圍。)的方式app

@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 註解的方式實現就不作演示了。maven

3. request——每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效

request只適用於Web程序,每一次 HTTP 請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效,當請求結束後,該對象的生命週期即告結束。 在 XML 中將 bean 定義成 request ,能夠這樣配置:ide

<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註解來指定 initdestroy 方法。這兩個註解均在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 implements   ApplicationContextAware,
        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):

與之比較相似的中文版本:

其實不少時候咱們並不會真的去實現上面說描述的那些接口,那麼下面咱們就除去那些接口,針對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的生命週期。 在這裏插入圖片描述 QQ討論羣組:984370849 706564342 歡迎加入討論

想要深刻學習的同窗們能夠加入QQ羣討論,有全套資源分享,經驗探討,沒錯,咱們等着你,分享互相的故事! 在這裏插入圖片描述

相關文章
相關標籤/搜索