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

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

與其他類型的scope相比,spring不完全管理scope為prototype的生命周期,spring容器僅僅是實例化、配置和組裝1個實例給客戶,而沒有進1步記錄它的狀態。Spring容器會履行所有bean的初始化方法,但是對scope為prototype的bean來講,其destruction生命周期函數不會被履行,所以其持有的昂貴的資源需要客戶端自己釋放。
下面的代碼進1步理解,首先是Client1和client2 兩個類,他們完全1樣,而AccoutDao是1個空類,甚么也沒有:
package com.test.scope.si;
/**
* @Description:有3個 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個 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 區分
他們都有3個依賴,分別是以上定義的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>
下面是測試程序:
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");
//測試scope為singleton 和prototype 區分,以下3個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"));
}
}
測試結果,符合預期:
其他Scope
除以上兩種scope,還有request, session和global session3種scope,他們用于web環境中,否則將拋出異常。
其bean的scope和以上類似,不同的是他們的意義。意義見開頭的扼要說明,具體的代碼例子見下節,下面說下以上3種scope的測試環境。
要測試這3種scope需要使用web項目,web項目部署較為復雜,本文依照spring官方文檔和網上的1些資料,把spring和servlet整合,做簡單的測試,下面的配置是 web.xml中配置。然后相應的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方法中手動注入依賴:
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
另外關于Spring的作用域和RequestContextListener作用
不同scope依賴
Spring容器中各個bean中可能存在依賴,而且相互依賴的bean之間的scope可能也不同。不同的scope之間的依賴可能會出現問題。主要是以下兩種:
singleton 依賴prototype;
reques、session、application作為依賴。
singleton 依賴prototype
依賴無處不在,當這類情況出現的時候,由于singleton 只實例化1次,所以其所依賴的prototype 的bean也只有1次被被設置依賴的機會。這有時不是我們想要的。
假定 A的scope為singleton,B的scope為prototype ,A依賴B。在調用A的某些方法時,需要全新的B的協作。因而bean定義依賴的方法就會出現問題。
有3種種解決方案:
Spring接口手動注入
在調用相干方法的時候,手動重新注入prototype的bean,這類方法可讓bean實現ApplicationContextAware 接口,獲得spring容器的援用,從中獲得bean。這樣做的壞處是和spring的接口耦合在了1起。示例代碼以下:
定義了1個全新的類ClientForSp,里面的方法testOne和testTwo都是打印本身,不同的是testOne做了重新注入的處理,有兩個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:測試 singleton依賴prototype ,實現ApplicationContextAware接口,獲得bean工廠的援用
* 兩個依賴字段均為prototype,
* 方法testOne 和 testTwo 均需要依賴字段的協助。
* 方法testOne 每次調用重新獲得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 依賴兩個 prototype的bean,但在ClientForSp方法testOne中利用spring接口手動注入-->
<bean id ="csp1" class="com.test.scope.sid.ClientForSp">
<property name="accoutDao" ref="account3"></property>
<property name="accoutDao1" ref="account3"></property>
</bean>
測試代碼以下,首先調用兩次testTwo方法(沒有重新注入依賴),然后調用testOne方法(重新注入了依賴):
ClientForSp sp1 = context.getBean("csp1", ClientForSp.class);
sp1.testTwo();
sp1.testTwo();
System.out.println();
sp1.testOne();
sp1.testOne();
測試結果以下,兩次調用testTwo方法的打印完全1樣,而后調用testOne方法可以看到accoutDao的援用每次都不1樣,符合預期:

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

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