配置文件中定義 Bean 時(shí),我們不但可以配置 Bean 的屬性值以及相互之間的依賴關(guān)系,還可以定義 Bean 的作用域 。作用域會對 Bean 的生命周期和創(chuàng)建方式產(chǎn)生影響 。
Bean 的作用域類型:
| 類型 | 說明 |
|---|---|
| singleton | 在 Spring 容器中僅存在一個(gè) Bean 實(shí)例, Bean 以單例的形式存在。 |
| prototype | 每次從容器中調(diào)用 Bean 時(shí),都會返回一個(gè)新的實(shí)例,即相當(dāng)于執(zhí)行 new XxxBean() 的實(shí)例化操作。 |
| request | 每次 http 請求都會創(chuàng)建一個(gè)新的 Bean , 僅用于 WebApplicationContext 環(huán)境。 |
| session | 同一個(gè) http Session 共享一個(gè) Bean ,不同的 http Session 使用不同的 Bean,僅用于 WebApplicationContext 環(huán)境。 |
| globalSession | 同一個(gè)全局 Session 共享一個(gè) bean, 用于 Porlet, 僅用于 WebApplication 環(huán)境。 |
低版本的 Spring 中,僅支持兩個(gè) Bean 作用域(singleton 與 prototype),所以之前的配置為 singleton=true/false。Spring 為了向后兼容,依然支持這種配置方式。我們推薦采用新的配置方式 scope=<作用域類型>。
1 singleton 作用域
Spring 以容器的方式,使得我們僅需配置,即可得到天然的單例模式。
一般情況下,無狀態(tài)或者狀態(tài)不可變的類適合使用單例模式來實(shí)現(xiàn), 不過 Spring 利用 AOP 和 LocalThread 的能力,對非線程安全的變量(狀態(tài))進(jìn)行了特殊處理,使的一些非線程安全的類(持有 Connection 的 DAO 類)變成了線程安全的類 。
因?yàn)?Spring 的超強(qiáng)能力,所以在實(shí)際應(yīng)用中,大部分 Bean 都能以單例方式運(yùn)行 ,這也是 bean 的默認(rèn)作用域指定為 singleton 的原因 。
singleton 的 Bean 在同一個(gè) Spring IoC 容器中只會一個(gè)實(shí)例。
配置:
<!-- singleton 作用域 -->
<bean id="author" class="net.deniro.spring4.bean.Author" scope="singleton"/>
<bean id="book1" class="net.deniro.spring4.bean.Book" p:author-ref="author"/>
<bean id="book2" class="net.deniro.spring4.bean.Book" p:author-ref="author"/>
<bean id="book3" class="net.deniro.spring4.bean.Book" p:author-ref="author"/>
單元測試:
System.out.println(((Book) context.getBean("book1")).getAuthor().hashCode());
System.out.println(((Book) context.getBean("book2")).getAuthor().hashCode());
System.out.println(((Book) context.getBean("book3")).getAuthor().hashCode());
輸出結(jié)果:
813656972
813656972
813656972
這證明 Author 類在容器中是一個(gè)單例。

不僅在配置文件中注入的 author 引用的是同一個(gè) author Bean,任何通過容器的 getBean("author") 返回的實(shí)例,引用的也是同一個(gè) Bean。
默認(rèn)情況下, Spring 的 ApplicationContext 容器在啟動(dòng)時(shí),會自動(dòng)實(shí)例化所有 singleton 的 Bean 并緩存在容器中 。 雖然啟動(dòng)時(shí)會多花費(fèi)一些時(shí)間,但是有這些好處:
- 對 Bean 提前進(jìn)行實(shí)例化操作會及早發(fā)現(xiàn)一些潛在的配置問題。
- Bean 以緩存的方式保存,當(dāng)運(yùn)行時(shí)調(diào)用該 Bean 時(shí)就無須再次實(shí)例化咯,因此提高運(yùn)行效率 。
如果不希望在容器啟動(dòng)時(shí)提前實(shí)例化 singleton 的 Bean ,那么可以通過 lazy-init 屬性進(jìn)行控制:
<bean id="book1" class="net.deniro.spring4.bean.Book" p:author-ref="author" lazy-init="true"/>
如果某個(gè) Bean 被其他需要提前實(shí)例化的 Bean 所引用,那么它即使配置了 lazy-init="true" ,也仍然會被提前實(shí)例化。
2 prototype 作用域
配置了 scope="prototype" 的 bean 為非單例作用域。
<bean id="author10" class="net.deniro.spring4.bean.Author" scope="prototype"/>
<bean id="book11" class="net.deniro.spring4.bean.Book" p:author-ref="author10"/>
<bean id="book12" class="net.deniro.spring4.bean.Book" p:author-ref="author10"/>
<bean id="book13" class="net.deniro.spring4.bean.Book" p:author-ref="author10"/>
單元測試:
System.out.println(((Book) context.getBean("book11")).getAuthor().hashCode());
System.out.println(((Book) context.getBean("book12")).getAuthor().hashCode());
System.out.println(((Book) context.getBean("book13")).getAuthor().hashCode());
輸出結(jié)果:
2048425748
1863932867s
1373810119
這證明 Author 類在容器中是不同的實(shí)例。

在默認(rèn)情況下, Spring 容器在啟動(dòng)時(shí)不實(shí)例化 prototype 的 bean ,此外, Spring 容器將 prototype 的 bean 交給調(diào)用者后,就不再負(fù)責(zé)管理它的生命周期咯。
3 與 Web 應(yīng)用環(huán)境相關(guān)的作用域
如果使用 Spring 的 WebApplicationContext ,則可以使用另外 3 種 Bean 的作用域 (request、session 和 gloableSession)。
3.1 配置監(jiān)聽器
首先必須在 Web 容器中進(jìn)行一些配置:
高版本的 Web 容器( Servlet 2.3 +)中,配置 http 請求監(jiān)聽器:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
低版本的 Web 容器,需要配置 http 請求過濾器(RequestContextFilter)。
它與 ContextLoaderListener 的區(qū)別是:
- ContextLoaderListener 實(shí)現(xiàn)了 ServletContextListener 監(jiān)聽器接口, 它只負(fù)責(zé)監(jiān)聽 web 容器的啟動(dòng)和關(guān)閉事件 。
- RequestContextListener 實(shí)現(xiàn)了 ServletRequestListener 監(jiān)聽器接口,它監(jiān)聽 HTTP 請求事件, Web 服務(wù)器的每一次請求都會通知它 。
3.2 request 作用域
request 作用域的 Bean 對應(yīng)一個(gè) HTTP 請求和生命周期 。
假設(shè):
<bean id="author" class="net.deniro.spring4.bean.Author" scope="request"/>
每次 HTTP 請求調(diào)用 author Bean 時(shí), Spring 容器就會創(chuàng)建一個(gè)新的 author Bean ;請求處理完畢,就會銷毀這個(gè) Bean。
3.3 session 作用域
假設(shè):
<bean id="author" class="net.deniro.spring4.bean.Author" scope="session"/>
author Bean 的作用于橫跨整個(gè) HTTP Session。Session 中的所有 HTTP 請求會共享同一個(gè) author Bean. 只有當(dāng) HTTP Session 結(jié)束后,author 實(shí)例才會被銷毀 。
3.4 globalSession 作用域
假設(shè):
<bean id="author" class="net.deniro.spring4.bean.Author" scope="globalSession"/>
globalSession 的作用域類似于 session 作用域, 不過僅在 Portlet 的 Web 應(yīng)用中使用 。 Portlet 定義了全局 Session,它被組成 Portlet Web 應(yīng)用的所有子 Portlet 共享。如果不在 Portlet 的 Web 應(yīng)用下,globalSession 等價(jià)于 session。
4 作用域依賴
假設(shè)我們需要在 非 web 作用域下的 Bean 中引用 web 作用域下的 Bean,那么這里我們就需要使用 Spring AOP 的功能。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
">
<!-- 非 web 作用域引用 web 作用域-->
<bean id="author20" class="net.deniro.spring4.bean.Author" scope="request">
<!-- 創(chuàng)建代理-->
<aop:scoped-proxy/>
</bean>
<bean id="book20" class="net.deniro.spring4.bean.Book" p:author-ref="author20"/>
</beans>
首先引入了 AOP 文件頭說明。
author Bean 的作用域是 request , 它被 singleton 作用域的 book Bean 所引用 。 為了使 book 能從 request 的作用域中獲取 author 的引用, 這里使用了 Spring AOP 為 book Bean 聲明了一個(gè)代理類。
當(dāng) book Bean 在 web 環(huán)境中調(diào)用 author Bean 時(shí), Spring AOP 將啟動(dòng)動(dòng)態(tài)代理機(jī)制智能判斷 author Bean 處于哪一個(gè) HTTP 請求線程中,并從對應(yīng)的 HTTP 請求線程中回去對應(yīng)的 author Bean。

Spring 通過動(dòng)態(tài)代理技術(shù),能夠讓單例的 book bean 引用到對應(yīng) HTTP 請求的 author Bean。
動(dòng)態(tài)代理會判斷當(dāng)前的 book 位于哪一個(gè)線程中,然后根據(jù)這個(gè)線程找到對應(yīng)的 HttpRequest,接著再從 HttpRequest 中獲取對應(yīng)的 author。根據(jù)容器的特性,一個(gè) HTTP 請求對應(yīng)一個(gè)獨(dú)立的線程。
因?yàn)?Java 語言只能對接口實(shí)現(xiàn)自動(dòng)代理,因此,Spring 是通過 CGLib 庫來實(shí)現(xiàn)類的動(dòng)態(tài)代理的,所以如果有需要,請?jiān)陬惵窂街屑尤?CGLib 庫。