spring之bean的Scope
來源:程序員人生 發(fā)布時(shí)間:2015-04-09 09:16:30 閱讀次數(shù):3653次
上篇文章較為詳細(xì)了對(duì)各種情況下bean依賴的配置做了說明,但1直沒有對(duì)Scope這個(gè)屬性進(jìn)行說明。本篇將1起學(xué)習(xí)。本文演示代碼下載地址
當(dāng)我們?cè)趚ml配置文件中配置1個(gè)bean的定義的時(shí)候,可以認(rèn)為是配置了1個(gè)模板,可以根據(jù)這個(gè)模板來生成很多個(gè)對(duì)象來滿足全部利用程序的依賴關(guān)系,同時(shí)我們也能夠配置對(duì)象的Scope。
Scope可以理解為SpringIOC容器中的對(duì)象應(yīng)當(dāng)處的限定場(chǎng)景或說該對(duì)象的存活空間,即在IOC容器在對(duì)象進(jìn)入相應(yīng)的scope之前,生成并裝配這些對(duì)象,在該對(duì)象不再處于這些scope的限定以后,容器通常會(huì)燒毀這些對(duì)象。
截止到目前為止,spring提供了6種類型的scope:
1. singleton 表示在spring容器中的單例,通過spring容器取得該bean時(shí)總是返回唯1的實(shí)例
2. prototype 表示每次取得bean都會(huì)生成1個(gè)新的對(duì)象
3. request 表示在1次http要求內(nèi)有效(只適用于web利用)
4. session 表示在1個(gè)用戶會(huì)話內(nèi)有效(只適用于web利用)
5. global Session 與上面類似,但是用于移動(dòng)裝備中的服務(wù)器中。
6. globalSession 表示在全局會(huì)話內(nèi)有效(只適用于web利用)
1般情況下,前兩種的scope就已足夠滿足需求,后幾種應(yīng)用于web容器中,默許的scope是singleton。
注:spring3.0開始提供 SimpleThreadScope
,但是默許沒有注冊(cè)。
單例
基本
Singleton 是spring容器默許采取的scope。注意這里的Singleton和設(shè)計(jì)模式中所描寫的概念不同。設(shè)計(jì)模式中指每一個(gè)classLoader1個(gè)類只有1個(gè)實(shí)例,而這里指每一個(gè)Spring容器對(duì)1個(gè) beandefinition只有1個(gè)實(shí)例。
見下圖說明:

下節(jié)中的代碼集中說明Singleton和prototype 。
懶加載
默許情況下,Singleton的bean是在spring容器初始化的進(jìn)程中進(jìn)行初始化的,這樣做的好處是可以盡早的發(fā)現(xiàn)配置的毛病。
但是如果有需要的話,可以取消這個(gè)機(jī)制,使用bean標(biāo)簽的lazy-init 屬性 ,以下:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
也能夠在配置文件根標(biāo)簽beans 使用以下配置,取消預(yù)加載的行動(dòng):
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
原型
Scope為prototype 的bean定義,每次需要的時(shí)候總是重新創(chuàng)建1個(gè)全新的對(duì)象,不論是依賴注入的需要,還是調(diào)用applicationContext的getBean方法。以下圖說明:

與其他類型的scope相比,spring不完全管理scope為prototype的生命周期,spring容器僅僅是實(shí)例化、配置和組裝1個(gè)實(shí)例給客戶,而沒有進(jìn)1步記錄它的狀態(tài)。Spring容器會(huì)履行所有bean的初始化方法,但是對(duì)scope為prototype的bean來講,其destruction生命周期函數(shù)不會(huì)被履行,所以其持有的昂貴的資源需要客戶端自己釋放。
下面的代碼進(jìn)1步理解,首先是Client1和client2 兩個(gè)類,他們完全1樣,而AccoutDao是1個(gè)空類,甚么也沒有:
package com.test.scope.si;
/**
* @Description:有3個(gè) AccoutDao 的援用,他們的scope順次為默許、單例(singleton)、原型( prototype)
* 省略的get set方法
*/
public class Client1 {
private AccoutDao accoutDao;
private AccoutDao accoutDaob;
private AccoutDao accoutDaoc;
@Override
public String toString() {
return "Client1 [accoutDao=" + accoutDao + ", accoutDaob=" + accoutDaob + ", accoutDaoc=" + accoutDaoc + "]";
}
}
下面是配置文件:
<?xml version="1.0" encoding="UTF⑻"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義3個(gè) AccoutDao bean 順次為默許配置 單例原型 -->
<bean id="account1" class="com.test.scope.si.AccoutDao"></bean>
<bean id="account2" class="com.test.scope.si.AccoutDao" scope="singleton"></bean>
<bean id="account3" class="com.test.scope.si.AccoutDao" scope="prototype"></bean>
<!-- 定義 Client1 bean 和 Client2 bean 他們的定義完全1樣,這里只是為了說明
singleton 和 prototype 區(qū)分
他們都有3個(gè)依賴,分別是以上定義的bean
-->
<bean id ="c1a" class="com.test.scope.si.Client1">
<property name="accoutDao" ref="account1"></property>
<property name="accoutDaob" ref="account2"></property>
<property name="accoutDaoc" ref="account3"></property>
</bean>
<bean id ="c2a" class="com.test.scope.si.Client2">
<property name="accoutDao" ref="account1"></property>
<property name="accoutDaob" ref="account2"></property>
<property name="accoutDaoc" ref="account3"></property>
</bean>
</beans>
下面是測(cè)試程序:
package com.test.scope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("allbean.xml");
//測(cè)試scope為singleton 和prototype 區(qū)分,以下3個(gè)bean的定義順次為默許、singleton 和 prototype 。
//注意 打印的值和后邊的 c1a和 c2a的相同和不同的地方
System.out.println(context.getBean("account1"));
System.out.println(context.getBean("account2"));
System.out.println(context.getBean("account3"));
//注意以下的不同的地方,可以看到accoutDaoc 的值總是不1樣的。而其他的與上面的打印保持1致。
System.out.println(context.getBean("c1a"));
System.out.println(context.getBean("c2a"));
}
}
測(cè)試結(jié)果,符合預(yù)期:
其他Scope
除以上兩種scope,還有request, session和global session3種scope,他們用于web環(huán)境中,否則將拋出異常。
其bean的scope和以上類似,不同的是他們的意義。意義見開頭的扼要說明,具體的代碼例子見下節(jié),下面說下以上3種scope的測(cè)試環(huán)境。
要測(cè)試這3種scope需要使用web項(xiàng)目,web項(xiàng)目部署較為復(fù)雜,本文依照spring官方文檔和網(wǎng)上的1些資料,把spring和servlet整合,做簡(jiǎn)單的測(cè)試,下面的配置是 web.xml中配置。然后相應(yīng)的servlet在init方法中從容器中取得依賴:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/webAllbean.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
Servlet中的init方法中手動(dòng)注入依賴:
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generatedmethod stub
super.init(config);
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(this
.getServletContext());
c = applicationContext.getBean("c3a", Client3.class);
System.out.println("iniiiiiii-------------");
}
注:需要引入aop的相干依賴包。
還有1些其他的方法:
Servlet代理:http://blog.csdn.net/xwl617756974/article/details/7451773
支持autowire的servlet: http://www.tuicool.com/articles/32U3Qr
另外關(guān)于Spring的作用域和RequestContextListener作用
不同scope依賴
Spring容器中各個(gè)bean中可能存在依賴,而且相互依賴的bean之間的scope可能也不同。不同的scope之間的依賴可能會(huì)出現(xiàn)問題。主要是以下兩種:
singleton 依賴prototype;
reques、session、application作為依賴。
singleton 依賴prototype
依賴無(wú)處不在,當(dāng)這類情況出現(xiàn)的時(shí)候,由于singleton 只實(shí)例化1次,所以其所依賴的prototype 的bean也只有1次被被設(shè)置依賴的機(jī)會(huì)。這有時(shí)不是我們想要的。
假定 A的scope為singleton,B的scope為prototype ,A依賴B。在調(diào)用A的某些方法時(shí),需要全新的B的協(xié)作。因而bean定義依賴的方法就會(huì)出現(xiàn)問題。
有3種種解決方案:
Spring接口手動(dòng)注入
在調(diào)用相干方法的時(shí)候,手動(dòng)重新注入prototype的bean,這類方法可讓bean實(shí)現(xiàn)ApplicationContextAware 接口,獲得spring容器的援用,從中獲得bean。這樣做的壞處是和spring的接口耦合在了1起。示例代碼以下:
定義了1個(gè)全新的類ClientForSp,里面的方法testOne和testTwo都是打印本身,不同的是testOne做了重新注入的處理,有兩個(gè)AccoutDao類型的prototype依賴用來比較,AccoutDao和以上相同:
package com.test.scope.sid;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.test.scope.si.AccoutDao;
/**
* @Description:測(cè)試 singleton依賴prototype ,實(shí)現(xiàn)ApplicationContextAware接口,獲得bean工廠的援用
* 兩個(gè)依賴字段均為prototype,
* 方法testOne 和 testTwo 均需要依賴字段的協(xié)助。
* 方法testOne 每次調(diào)用重新獲得bean,故每次都是全新的bean。
* 方法testTwo則不是。
*
*/
public class ClientForSp implements ApplicationContextAware{
private AccoutDao accoutDao;
private AccoutDao accoutDao1;
private ApplicationContext context;
public void testOne(){
this.setAccoutDao(createAccountDao());
System.out.println(this);
}
public void testTwo(){
System.out.println(this);
}
public AccoutDao getAccoutDao() {
return accoutDao;
}
public void setAccoutDao(AccoutDao accoutDao) {
this.accoutDao = accoutDao;
}
public AccoutDao getAccoutDao1() {
return accoutDao1;
}
public void setAccoutDao1(AccoutDao accoutDao1) {
this.accoutDao1 = accoutDao1;
}
public AccoutDao createAccountDao(){
return context.getBean("account3", AccoutDao.class);
}
@Override
public String toString() {
return "ClientForSp [accoutDao=" + accoutDao + ", accoutDao1=" + accoutDao1 + "]";
}
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
this.context = arg0;
}
}
配置文件以下:
<!-- 定義 ClientForSp 依賴兩個(gè) prototype的bean,但在ClientForSp方法testOne中利用spring接口手動(dòng)注入-->
<bean id ="csp1" class="com.test.scope.sid.ClientForSp">
<property name="accoutDao" ref="account3"></property>
<property name="accoutDao1" ref="account3"></property>
</bean>
測(cè)試代碼以下,首先調(diào)用兩次testTwo方法(沒有重新注入依賴),然后調(diào)用testOne方法(重新注入了依賴):
ClientForSp sp1 = context.getBean("csp1", ClientForSp.class);
sp1.testTwo();
sp1.testTwo();
System.out.println();
sp1.testOne();
sp1.testOne();
測(cè)試結(jié)果以下,兩次調(diào)用testTwo方法的打印完全1樣,而后調(diào)用testOne方法可以看到accoutDao的援用每次都不1樣,符合預(yù)期:

Lookup methodinjection
spring容器可以通過查找其所管理的已命名的bean作為返回值,來覆蓋(override)其所管理的bean的某個(gè)方法(查找到的bean作為bean的返回值)。利用這點(diǎn)可以解決以上問題。方法覆蓋采取動(dòng)態(tài)生成字節(jié)碼的情勢(shì)。被覆蓋的方法可以是抽象的,也能夠是非抽象的,需要沒有任何參數(shù)。
方法簽名以下:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
解決以上問題,需要使用bean標(biāo)簽的<lookup-method>子標(biāo)簽,示例代碼和以上類似,只有配置不同,配置以下:
<!-- 定義 ClientForSp ,依賴兩個(gè) prototype的bean,采取 lookup-method方法,在每次調(diào)用testOne時(shí),重新注入新的prototype bean -->
<bean id ="csp2" class="com.test.scope.sid.ClientForSp">
<lookup-method name="createAccountDao" bean="account3"/>
<property name="accoutDao1" ref="account3"></property>
</bean>
測(cè)試代碼和結(jié)果不再贅述。
Arbitrary methodreplacement
這是1個(gè)不太經(jīng)常使用的方法。需要額外的1個(gè)類實(shí)現(xiàn)MethodReplacer接口,故名思議,用來做方法替換的。
另外需要使用bean標(biāo)簽的<lookup-method>子標(biāo)簽<replaced-method>,示例代碼和以上類似,注意配置和MethodReplacer的實(shí)現(xiàn):
<!-- 定義 ClientForSp ,依賴兩個(gè) prototype的bean,采取 replaced-method方法,強(qiáng)迫替換掉 createAccountDao 方法-->
<bean id ="csp3" class="com.test.scope.sid.ClientForSp">
<replaced-method name="createAccountDao" replacer="myReplacer"></replaced-method>
<property name="accoutDao" ref="account3"></property>
<property name="accoutDao1" ref="account3"></property>
</bean>
<bean id ="myReplacer" class="com.test.scope.sid.MyAccountCreateor"/>
public class MyAccountCreateor implements MethodReplacer {
/**
* 參數(shù)的意思分別是原被調(diào)用的方法對(duì)象、方法和參數(shù)
*/
@Override
public Object reimplement(Object arg0, Method arg1, Object[] arg2) throws Throwable {
System.out.println("replaced");
return new AccoutDao();
}
}
測(cè)試代碼和分析不在贅述。
reques、session、application作為依賴。
當(dāng)scope為 request, session, globalSession的bean作為依賴注入到其他范圍內(nèi)的bean中時(shí),會(huì)產(chǎn)生類似singleton依賴prototype的問題。這類情況下只要使用bean的子標(biāo)簽<aop:scoped-proxy/> 便可。
以下:
<!-- an HTTP request bean exposed as a proxy -->
<bean id="account4" class="com.test.scope.si.AccoutDao" scope="request">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with the above beans -->
<bean id ="c3a" class="com.test.scope.si.Client3">
<property name="accoutDao" ref="account4"></property>
</bean>
雖然代碼很簡(jiǎn)單,但是要理解這個(gè)問題。由于c3a的scope為singleton,所以它只被初始化1次,它的依賴accoutDao的scope雖然是request,也只被注入1次,當(dāng)是不同httpRequest到來是,bean “c3a”的accoutDao依賴總是不變的,這肯定是毛病的。所以加上<aop:scoped-proxy/>子標(biāo)簽,告知spring容器采取aop代理為不同request生成不同的accoutDao對(duì)象。1般采取動(dòng)態(tài)生成字節(jié)碼的技術(shù)。如果不使用<aop:scoped-proxy/>標(biāo)簽,則啟動(dòng)時(shí)報(bào)錯(cuò)。
下面使用具體的代碼演示 request、session 的scope 使用Aop代理前后的區(qū)分。代碼需要web環(huán)境,環(huán)境的部署見上文。這里配置1個(gè)bean具有兩個(gè)依賴,順次為requset代理,session代理。
配置以下:
<!-- an HTTP request bean exposed as a proxy -->
<bean id="account4" class="com.test.scope.si.AccoutDao" scope="request">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- an HTTP session bean exposed as a proxy -->
<bean id="account5" class="com.test.scope.si.AccoutDao" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with the above beans -->
<bean id ="c3a" class="com.test.scope.si.Client3">
<property name="accoutDao" ref="account4"></property>
<property name="accoutDao1" ref="account5"></property>
</bean>
類client3試試是1個(gè)標(biāo)準(zhǔn)的javabean,兩個(gè)字段和以上的依賴對(duì)應(yīng),這里不贅述。測(cè)試代碼在1個(gè)servlet中,以下是它的doget方法:
if (c != null) {
System.out.println("request代理每次都不1樣"+c.getAccoutDao());
System.out.println("session代理不同會(huì)話不1樣"+c.getAccoutDao1());
} else {
System.out.println("null");
}
把以上代碼部署到tomcat中,然后分別用不同的閱讀器各訪問兩次,不同的閱讀器來摹擬不同的session,測(cè)試結(jié)果以下:

以上測(cè)試結(jié)果是先用電腦閱讀器訪問兩次,在用手機(jī)閱讀器訪問兩次。首先,每次訪問request依賴的值都不1樣。其次,相同閱讀器的session依賴1樣,不同閱讀器不1樣。以上符合預(yù)期結(jié)果。
結(jié)束
本篇文章較為詳細(xì)了介紹了spring bean的scope屬性,文中有詳細(xì)的示例測(cè)試代碼。從scope到不同的scope之間的依賴關(guān)系,特別是在說明有關(guān)web的scope的時(shí)候,本人花費(fèi)了較多的時(shí)間來部署環(huán)境。關(guān)于scope還缺少自定義scope的部份,暫且不討論。期待大家共同進(jìn)步。本文演示代碼下載地址
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)