Spring Bean 的生命周期及線程安全

Spring Bean 的生命周期

  • Instantiate:IOC 容器在 XML 文件中找到 Bean 的定義并實例化
  • Populate Properties:IOC 容器通過 DI 注入屬性
  • Set Bean Name:如果該 Bean 實現(xiàn)了 BeanNameAware 接口,則 IOC 容器會將 Bean 的 id 傳給 setBeanName() 方法
  • Set Bean Factory:如果該 Bean 實現(xiàn)了 BeanFactoryAware 接口,則 IOC 容器會將 Bean 的 BeanFactory 對象傳給 setBeanFactory() 方法
  • Pre Initialization:也叫 Post Process。調(diào)用 postProcessBeforeInitialization() 方法
  • Initialize Bean:如果該 Bean 實現(xiàn)了 InitializingBean 接口,則 IOC 容器會將會調(diào)用 afterPropertySet() 方法
  • Post Initialization:調(diào)用 postProcessAfterInitialization() 方法
  • Ready to use
  • Destroy

Bean 的初始化回調(diào)和銷毀回調(diào)

方式1:實現(xiàn)特定的接口 InitializingBean 和 DisposableBean

public class MyBean implements InitializingBean, DisposableBean {
  // Bean 的初始化回調(diào)
  public void afterPropertySet() {
    ...
  }
  // Bean 的銷毀回調(diào)
  public void destroy() {
    ...
  }
}

方式2:通過 XML 配置

<bean id="mybean" class="MyBean" 
<!-- Bean 的初始化回調(diào),在類中定義 init 方法 -->
init_method="init"
<!-- Bean 的銷毀回調(diào),在類中定義 destroy 方法 -->
destroy_method="destroy"
/>

Spring Bean 的 Scope

在 Spring2.0 之前 Bean 只有 2 種作用域即:singleton(單例)、non-singleton(也稱 prototype)。
Spring 2.0 以后,增加了 session、request、global session 三種專用于 Web 應(yīng)用程序上下文的 Bean。

  • singleton (scope 默認值)
    設(shè)置:<bean id="myBean" class="MyBean" scope="singleton" />
    Spring IOC 容器只會創(chuàng)建該 Bean 的唯一實例。
    所有對 Bean的請求,例如 appContext.getBean("myBean");,只要 id 與該 Bean 定義相匹配,都會返回 Bean 的同一實例。

  • prototype
    設(shè)置:<bean id="myBean" class="MyBean" scope="prototype" />
    每一次請求都會產(chǎn)生一個新的 Bean 實例,相當(dāng)于一個 new 的操作。

  • request
    設(shè)置:<bean id="myBean" class="MyBean" scope="request" />
    每一次 HTTP 請求都會產(chǎn)生一個新的 Bean,同時該 Bean 僅在當(dāng)前 HTTP request 內(nèi)有效。

  • session
    設(shè)置:<bean id="myBean" class="MyBean" scope="session" />
    每一次 HTTP 請求都會產(chǎn)生一個新的 Bean,同時該 Bean 僅在當(dāng)前 HTTP session 內(nèi)有效。

  • global session
    設(shè)置:<bean id="myBean" class="MyBean" scope="global session" />
    global session 作用域類似于標(biāo)準(zhǔn)的 HTTP Session 作用域,不過它僅僅在基于 Portlet 的 web 應(yīng)用中才有意義。Portlet 規(guī)范定義了全局 Session 的概念,它被所有構(gòu)成某個 Portlet Web 應(yīng)用的各種不同的 Portlet 所共享。

Spring Bean 線程安全

我們交由 Spring 管理的大多數(shù)對象其實都是一些無狀態(tài)的對象,這種不會因為多線程而導(dǎo)致狀態(tài)被破壞的對象很適合 Spring 的默認 scope,每個單例的無狀態(tài)對象都是線程安全的(也可以說只要是無狀態(tài)的對象,不管單例多例都是線程安全的)。

無狀態(tài)的對象即是自身沒有狀態(tài)的對象,自然也就不會因為多個線程的交替調(diào)度而破壞自身狀態(tài)導(dǎo)致線程安全問題。無狀態(tài)對象包括我們經(jīng)常使用的 DO、DTO、VO 這些只作為數(shù)據(jù)的實體模型的貧血對象,還有 Service、DAO 和 Controller,這些對象并沒有自己的狀態(tài),它們只是用來執(zhí)行某些操作的。例如,每個 DAO 提供的函數(shù)都只是對數(shù)據(jù)庫的 CRUD,而且每個數(shù)據(jù)庫 Connection 都作為函數(shù)的局部變量,用完即關(guān)(或交還給連接池)。

有人可能會認為,我使用 request 作用域不就可以避免每個請求之間的安全問題了嗎?這是完全錯誤的,因為 Controller 默認是單例的,一個 HTTP 請求是會被多個線程執(zhí)行的,這就又回到了線程的安全問題。當(dāng)然,你也可以把 Controller 的 scope 改成 prototype,實際上 Struts2 就是這么做的,但有一點要注意,Spring MVC 對請求的攔截粒度是基于每個方法的,而 Struts2 是基于每個類的,所以把 Controller 設(shè)為多例將會頻繁的創(chuàng)建與回收對象,嚴重影響到了性能。

Spring 根本就沒有對 bean 的多線程安全問題做出任何保證與措施。對于每個 bean 的線程安全問題,根本原因是每個 bean 自身的設(shè)計。不要在 bean 中聲明任何有狀態(tài)的實例變量或類變量,如果必須如此,那么就使用 ThreadLocal 把變量變?yōu)榫€程私有的,如果 bean 的實例變量或類變量需要在多個線程之間共享,那么就只能使用 synchronized、lock、CAS 等這些實現(xiàn)線程同步的方法了。


引用:
聊一聊 Spring 中的線程安全性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容