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)線程同步的方法了。