spring之bean的Scope
上篇文章較為詳細了對各種情況下bean依賴的配置做了說明,但一直沒有對Scope這個屬性進行說明。本篇將一起學習。本文演示程式碼下載地址
當我們在xml配置檔案中配置一個bean的定義的時候,可以認為是配置了一個模板,可以根據這個模板來生成很多個物件來滿足整個應用程式的依賴關係,同時我們也可以配置物件的Scope。
Scope可以理解為SpringIOC容器中的物件應該處的限定場景或者說該物件的存活空間,即在IOC容器在物件進入相應的scope之前,生成並裝配這些物件,在該物件不再處於這些scope的限定之後,容器通常會銷燬這些物件。
截止到目前為止,spring提供了六種型別的scope:
1. singleton 表示在spring容器中的單例,通過spring容器獲得該bean時總是返回唯一的例項
2. prototype 表示每次獲得bean都會生成一個新的物件
3. request 表示在一次http請求內有效(只適用於web應用)
4. session 表示在一個使用者會話內有效(只適用於web應用)
5. global Session 與上面類似,但是用於移動裝置中的伺服器中。
6. globalSession 表示在全域性會話內有效(只適用於web應用)
一般情況下,前兩種的scope就已經足夠滿足需求,後幾種運用於web容器中,預設的scope是singleton。
單例
基本
Singleton 是spring容器預設採用的scope。注意這裡的Singleton和設計模式中所描述的概念不同。設計模式中指每個classLoader一個類只有一個例項,而這裡指每個Spring容器對於一個 beandefinition只有一個例項。
見下圖說明:
下節中的程式碼集中說明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定義,每一次需要的時候總是重新建立一個全新的物件,不論是依賴注入的需要,還是呼叫applicationContext的getBean方法。如下圖說明:
與其他型別的scope相比,spring不完全管理scope為prototype的生命週期,spring容器僅僅是例項化、配置和組裝一個例項給客戶,而沒有進一步記錄它的狀態。Spring容器會執行所有bean的初始化方法,但是對於scope為prototype的bean來說,其destruction生命週期函式不會被執行,所以其持有的昂貴的資源需要客戶端自己釋放。
下面的程式碼進一步理解,首先是Client1和client2 兩個類,他們完全一樣,而AccoutDao是一個空類,什麼也沒有:
package com.test.scope.si;
/**
* @Description:有三個 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-8"?>
<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">
<!-- 定義三個 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 他們的定義完全一樣,這裡只是為了說明
singleton 和 prototype 區別
他們都有三個依賴,分別是以上定義的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 區別,以下三個bean的定義依次為預設、singleton 和 prototype 。
//注意 列印的值和後邊的 c1a和 c2a的相同和不同之處
System.out.println(context.getBean("account1"));
System.out.println(context.getBean("account2"));
System.out.println(context.getBean("account3"));
//注意以下的不同之處,可以看到accoutDaoc 的值總是不一樣的。而其他的與上面的列印保持一致。
System.out.println(context.getBean("c1a"));
System.out.println(context.getBean("c2a"));
}
}
測試結果,符合預期:
其他Scope
除了以上兩種scope,還有request, session和global session三種scope,他們用於web環境中,否則將丟擲異常。
其bean的scope和以上類似,不同的是他們的意義。意義見開頭的簡要說明,具體的程式碼例子見下節,下面說下以上三種scope的測試環境。
要測試這三種scope需要使用web專案,web專案部署較為複雜,本文依照spring官方文件和網上的一些資料,把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的相關依賴包。
還有一些其他的方法:
Servlet代理:http://blog.csdn.net/xwl617756974/article/details/7451773
支援autowire的servlet: http://www.tuicool.com/articles/32U3Qr
不同scope依賴
Spring容器中各個bean中可能存在依賴,而且相互依賴的bean之間的scope可能也不同。不同的scope之間的依賴可能會出現問題。主要是以下兩種:
singleton 依賴prototype;
reques、session、application作為依賴。
singleton 依賴prototype
依賴無處不在,當這種情況出現的時候,由於singleton 只例項化一次,所以其所依賴的prototype 的bean也只有一次被被設定依賴的機會。這有時不是我們想要的。
假設 A的scope為singleton,B的scope為prototype ,A依賴B。在呼叫A的某些方法時,需要全新的B的協作。於是bean定義依賴的方法就會出現問題。
有三種種解決方案:
Spring介面手動注入
在呼叫相關方法的時候,手動重新注入prototype的bean,這種方法可以讓bean實現ApplicationContextAware 介面,獲取spring容器的引用,從中獲取bean。這樣做的壞處是和spring的介面耦合在了一起。示例程式碼如下:
定義了一個全新的類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方法的列印完全一樣,而後呼叫testOne方法可以看到accoutDao的引用每次都不一樣,符合預期:
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
這是一個不太常用的方法。需要額外的一個類實現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,所以它只被初始化一次,它的依賴accoutDao的scope儘管是request,也只被注入一次,當是不同httpRequest到來是,bean “c3a”的accoutDao依賴總是不變的,這肯定是錯誤的。所以加上<aop:scoped-proxy/>子標籤,告知spring容器採用aop代理為不同request生成不同的accoutDao物件。一般採用動態生成位元組碼的技術。如果不使用<aop:scoped-proxy/>標籤,則啟動時報錯。
下面使用具體的程式碼演示 request、session 的scope 使用Aop代理前後的區別。程式碼需要web環境,環境的部署見上文。這裡配置一個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試試是一個標準的javabean,兩個欄位和以上的依賴對應,這裡不贅述。測試程式碼在一個servlet中,如下是它的doget方法:
if (c != null) {
System.out.println("request代理每次都不一樣"+c.getAccoutDao());
System.out.println("session代理不同會話不一樣"+c.getAccoutDao1());
} else {
System.out.println("null");
}
把以上程式碼部署到tomcat中,然後分別用不同的瀏覽器各訪問兩次,不同的瀏覽器來模擬不同的session,測試結果如下:
以上測試結果是先用電腦瀏覽器訪問兩次,在用手機瀏覽器訪問兩次。首先,每次訪問request依賴的值都不一樣。其次,相同瀏覽器的session依賴一樣,不同瀏覽器不一樣。以上符合預期結果。
結束
本篇文章較為詳細了介紹了spring bean的scope屬性,文中有詳細的示例測試程式碼。從scope到不同的scope之間的依賴關係,尤其是在說明有關web的scope的時候,本人花費了較多的時間來部署環境。關於scope還缺少自定義scope的部分,暫且不討論。期待大家共同進步。本文演示程式碼下載地址
相關推薦
Spring之AOP
iss 功能 使用 row 相關 到你 術語 line poj 本文來介紹Spring的AOP。 為什麽使用AOP? 使用純面向對象的思想進行編程。那麽對象不但須要處理自身要負責的業務邏輯,要須要關心日誌、安全控制和事務。例如以下圖:
Spring之IOC篇章具體解釋
什麽 alt cti 反轉 獲得 ring 這樣的 圖片 多態 專題一 IOC 1.接口以及面向接口編程 a.結構設計中,分清層次以及調用關系,每層僅僅向外(或者上層)提供一組功能接口,各層間僅依賴接口而非實現類這樣做的優點是,接口實現的變動不影響各層
spring之註解(一)概述
dsm 的人 src article service 優缺點 中一 auto 數據 Spring的核心是依賴註入(DI),而依賴註入的基礎是依賴信息的配置。這些配置稱之為元數據。在之前的的學習中。一直採用的是基於xml的配置,這些元數據配置在spring內部被註冊成為B
Spring之IOC/DI(反轉控制/依賴註入)_入門Demo
pro 開發 string類 業務邏輯 測試類 控制 control odin 以及 軟件152 劉安民 在平時的java應用開發中,我們要實現某一個功能或者說是完成某個業務邏輯時至少需要兩個或以上的對象來協作完成,在沒有使用Spring的時候,每個對象在需要使用他的合作對
spring之pom.xml配置
java del ng- onf leaf pro location tro fin 軟件152 譚智馗 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache
Spring 之 @ComponentScan 探究以及 mock Spring MVC
.com nbsp glib sep exce 明顯 comm exc director 【 @ComponentScan 探究】 原文(錯):發現一件奇怪的事情,我明明沒有指定掃描那個包,卻能實實在在地 Autowired 那個包中的類的對象而不出現異常。 按理來說,
Spring之AOP的註解配置
函數 cts expr pro text bsp 定義 一個 rod 配置過程可以簡單的分為3步: 1,業務類配置 在業務類前加入,將業務類交由Spring管理 @Component("s") 這個表示,這個業務類的Bean名字為 s 。 2,將切點和切面
Spring之AOP在XML中的配置方法
字段 object 代理 [] ger 編程 調用 加載器 bsp AOP 即 Aspect Oriental Program 面向切面編程 先來一個栗子: <aop:config> <aop:pointcut id="
Spring之依賴註入
執行 模塊 連接 logs 編程 情況 png 配置文件 抽象 1.為什麽要有依賴註入? 對於依賴的對象要自己生成,緊耦合; 接口被傳入進來,具體傳進來的是什麽根據Spring容器決定(Quest可以有很多實現);使用的是哪個具體的Quest,BravaKnig
mybatis 整合spring之mapperLocations配置的問題
any 引入 location rop myba 路徑 通過 user configure 今天嘗試spring整合mybatis時遇到這麽一個問題,就是在配置sqlSessionFactory時是否要配置mapperLocations的問題。 <bean i
spring之控制反轉
構建 classpath col 技術 節點 ace version urn 星期 IOC (Inversion of Control) 控制反轉 我的理解:將創建對象的控制權從代碼本身轉交給了外部容器(spring容器)。 1.將組件對象(業務對象)的控制權從代碼本身轉移
spring之scope作用域
tle .cn lob height 模式 新建 應用 let image spring中,bean的作用域有五種類型:默認是單例模式, singleton prototype request session
spring之AspectJ實現AOP
添加 result exe 包名 inter string ring service odin 一、基於註解的AspectJ 1、要在spring應用中使用AspectJ註解,必須在classpath下包含AspectJ類庫: com.springsource.org
spring 之 lazy-init Autowired depends-on
bean 意思 ebe java 源碼分析 roo 啟動 register ont 1 lazy-init lazy-init是延遲初始化的意思。 spring中容器都是盡早的創建和配置所有的單例bean,因此當容器在啟動時,就會去配置和創建單例bean。 默認情況
spring 之 init-method & InitializingBean
設置 diff object eof off gist n) rri clas init-method 是bean (第一次)實例化的時候被調用的。 先看個異常: INFO: Overriding bean definition for bean ‘office
spring 之 lookup-method & replace-method
replace ins cer match generate declare rri 單例 原型 初始化bean的堆棧: at org.springframework.beans.factory.support.CglibSubclassingInstan
spring 之 lookup-method & replaced-method II
tail art bject 完全 過去的 spring 因此 player imp 顯然, lookup-method 的name 對應的方法 是可以有方法參數的, 但是, 我發現, 參數無法傳遞, 傳過去的參數好像被丟入了黑洞一般。。。 非常奇怪。 lookup-me
我們一起學習Spring之Spring簡介(一)
邏輯 style 發的 nfa 不同的 構建 john 局限 認識 首先聲明,我是一個spring初學者,寫這篇blog的目的是為了能和大家交流。文中不當之處還望大佬指出,不勝感激! 好了,現在我們開始進入正題。 很多小夥伴在學習Java的時候都會有人建議你去學
spring 之 BeanDefinition & BeanDefinitionParser
nconf ini spl 存在 for context nta item 當前 xml bean factory 的解析過程的 堆棧大概是這樣的: at org.springframework.beans.factory.xml.NamespaceHa
Spring之基礎
裝配bean 依賴註入 低耦合 負責 幫助 down 不同 舉例 class Spring之目的 ????Spring致力於簡化企業級Java開發,促進代碼的松散耦合。成功的關鍵在於依賴註入(DI)和面向切面編程(AOP) Spring之DI ????DI是組裝應用對象的一