Spring系列之bean的使用
一、Bean的定義
<bean id="userDao" class="com.dev.spring.simple.MemoryUserDao"/>
這是一個最簡單的 Bean 定義。它類似於調用了語句:
MemoryUserDao userDao = new MemoryUserDao()。
id屬性必須是一個有效的 XML ID,這意味著它在整個 XML 文檔中必須唯一。它是一個 Bean 的“終身代號”。同時你也可以用 name 屬性為 Bean 定義一個或多個別名(用逗號或空格分開多個別名)。name 屬性允許出現任意非法的 XML 字母。例如:
<bean id="userDao" name="userDao*_1, userDao*_2" class="com.dev.spring.simple.MemoryUserDao"/>。
class屬性定義了這個 Bean 的全限定類名(包名+類名)。Spring 能管理幾乎所有的 Java 類。一般情況,這個 Java 類會有一個默認的構造函數,用set方法設置依賴的屬性。
Bean 元素出了上面的兩個屬性之外,還有很多其它屬性。說明如下:
<bean id="beanId"(1) name="beanName"(2) class="beanClass"(3) parent="parentBean"(4) abstract="true | false"(5) singleton="true | false"(6) lazy-init="true | false | default"(7) autowire="no | byName | byType | constructor | autodetect | default"(8) dependency-check = "none | objects | simple | all | default"(9) depends-on="dependsOnBean"(10) init-method="method"(11) destroy-method="method"(12) factory-method="method"(13) factory-bean="bean">(14) </bean>
(1)id: Bean 的唯一標識名。它必須是合法的 XML ID,在整個 XML 文檔中唯一。
(2)name: 用來為 id 創建一個或多個別名。它可以是任意的字母符合。多個別名之間用逗號或空格分開。
(3)class: 用來定義類的全限定名(包名+類名)。只有子類 Bean 不用定義該屬性。
(4)parent: 子類 Bean 定義它所引用它的父類 Bean。這時前面的 class 屬性失效。子類 Bean 會繼承父類 Bean 的所有屬性,子類 Bean 也可以覆蓋父類 Bean 的屬性。註意:子類 Bean 和父類 Bean 是同一個 Java 類。
(5)abstract(默認為”false”):用來定義 Bean 是否為抽象 Bean。它表示這個 Bean 將不會被實例化,一般用於父類 Bean,因為父類 Bean 主要是供子類 Bean 繼承使用。
(6)singleton(默認為“true”):定義 Bean 是否是 Singleton(單例)。如果設為“true”,則在 BeanFactory 作用範圍內,只維護此 Bean 的一個實例。如果設為“flase”,Bean將是 Prototype(原型)狀態,BeanFactory 將為每次 Bean 請求創建一個新的 Bean 實例。
(7)lazy-init(默認為“default”):用來定義這個 Bean 是否實現懶初始化。如果為“true”,它將在 BeanFactory 啟動時初始化所有的 Singleton Bean。反之,如果為“false”,它只在 Bean 請求時才開始創建 Singleton Bean。
(8)autowire(自動裝配,默認為"default"):它定義了 Bean 的自動裝載方式。
"no":不使用自動裝配功能。
"byName":通過 Bean 的屬性名實現自動裝配。
"byType":通過 Bean 的類型實現自動裝配。
"constructor":類似於 byType,但它是用於構造函數的參數的自動組裝。
"autodetect":通過 Bean 類的反省機制(introspection)決定是使用“constructor”還是使用“byType”。
(9)dependency-check(依賴檢查,默認為“default”):它用來確保 Bean 組件通過 JavaBean 描述的所以依賴關系都得到滿足。在與自動裝配功能一起使用時,它特別有用。
none:不進行依賴檢查。
objects:只做對象間依賴的檢查。
simple:只做原始類型和 String 類型依賴的檢查
all:對所有類型的依賴進行檢查。它包括了前面的 objects 和 simple。
(10)depends-on(依賴對象):這個 Bean 在初始化時依賴的對象,這個對象會在這個 Bean 初始化之前創建。
(11)init-method:用來定義 Bean 的初始化方法,它會在 Bean 組裝之後調用。它必須是一個無參數的方法。
(12)destroy-method:用來定義 Bean 的銷毀方法,它在 BeanFactory 關閉時調用。同樣,它也必須是一個無參數的方法。它只能應用於singleton Bean。
(13)factory-method:定義創建該 Bean 對象的工廠方法。它用於下面的"factory-bean",表示這個 Bean 是通過工廠方法創建。此時,"class"屬性失效。
(14)factory-bean:定義創建該 Bean 對象的工廠類。如果使用了"factory-bean"則"class"屬性失效。
二、實例化Bean的三種方式
1.使用構造器實例化Bean
這是最簡單的方式,Spring IOC容器即能使用默認空構造器也能使用有參數構造器兩種方式創建Bean。
使用空構造器進行定義,class屬性指定的類必須有空構造器。使用有參數構造器進行定義,可以使用< constructor-arg >標簽指定構造器參數值,其中index表示位置,value表示常量值,也可以指定引用,指定引用使用ref來引用另一個Bean定義,後邊會詳細介紹。下面來看一個例子:
(1)定義一個接口
package com.spring.service; public interface IUserService { public void show(); }
(2)實現類,該類有一個空構造器和一個有參構造器:
package com.spring.service.impl; import com.spring.service.IUserService; public class UserServiceImpl implements IUserService{ private String message; public UserServiceImpl(){ this.message="lixiaoxi"; } public UserServiceImpl(String message){ this.message=message; } public void show(){ System.out.println("hello,"+message); } }
(3)在配置文件(applicationContext1.xml)中配置Bean定義,如下所示:
<!-- 使用默認構造函數 --> <bean id="bean1" class="com.spring.service.impl.UserServiceImpl">
</bean> <!-- 使用有參數構造函數 --> <bean id="bean2" class="com.spring.service.impl.UserServiceImpl" > <!-- 指定構造器參數 --> <constructor-arg index="0" value="zhangsan" /> </bean>
(4)測試方法:
/** * 使用構造器實例化Bean */ @Test public void testCreateBeanByConstructor(){ //讀取配置文件 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext1.xml"); //獲取bean的實例 IUserService bean1=(IUserService) ctx.getBean("bean1"); //調用方法 bean1.show(); IUserService bean2=(IUserService) ctx.getBean("bean2"); bean2.show(); }
2.使用靜態工廠方法實例化Bean
使用這種方式除了指定必須的class屬性,還要指定factory-method屬性來指定實例化Bean的方法,而且使用靜態工廠方法也允許指定方法參數,spring IoC容器將調用此屬性指定的方法來獲取Bean。
(1)定義靜態工廠類:
package com.spring.factory; import com.spring.service.IUserService; import com.spring.service.impl.UserServiceImpl; public class UserStaticFactory { //工廠方法 public static IUserService newInstance(String message){ //返回需要的Bean實例 return new UserServiceImpl(message); } }
(2)在配置文件(applicationContext1.xml)中配置Bean定義,如下所示:
<!-- 使用靜態工廠方法 --> <bean id="bean3" class="com.spring.factory.UserStaticFactory" factory-method="newInstance" > <constructor-arg index="0" value="lisi" /> </bean>
(3)測試方法:
/** * 使用靜態工廠實例化Bean */ @Test public void testCreateBeanByStaticFactory(){ ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext1.xml"); IUserService bean3=(IUserService) ctx.getBean("bean3"); bean3.show(); }
3.使用實例工廠方法實例化Bean
使用這種方式不能指定class屬性,此時必須使用factory-bean屬性來指定工廠Bean,factory-method屬性指定實例化Bean的方法,而且使用實例工廠方法允許指定方法參數,方式和使用構造器方式一樣,配置如下:
(1)定義實例工廠類:
package com.spring.factory; import com.spring.service.IUserService; import com.spring.service.impl.UserServiceImpl; public class UserInstanceFactory { public IUserService newInstance(String message){ return new UserServiceImpl(message); } }
(2)在配置文件(applicationContext1.xml)中配置Bean定義,如下所示:
<!-- 1.定義實例工廠Bean --> <bean id="beanInstanceFactory" class="com.spring.factory.UserInstanceFactory" /> <!-- 2.使用實例工廠Bean創建Bean --> <bean id="bean4" factory-bean="beanInstanceFactory" factory-method="newInstance" > <constructor-arg index="0" value="aaaa"></constructor-arg> </bean>
(3)測試方法:
/** * 使用實例工廠實例化Bean */ @Test public void testCreateBeanByInstanceFactory(){ ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext1.xml"); IUserService bean4=(IUserService) ctx.getBean("bean4"); bean4.show(); }
總結:
這三種方式只是配置不一樣,從獲取方式看完全一樣,沒有任何不同。這也是Spring IoC的魅力,Spring IoC幫你創建Bean,我們只管使用就可以了,是不是很簡單。
三、Bean的作用域
什麽是作用域呢?即“scope”,在面向對象程序設計中一般指對象或變量之間的可見範圍。而在Spring容器中是指其創建的Bean對象相對於其他Bean對象的請求可見範圍。
Spring提供“singleton”和“prototype”兩種基本作用域,另外提供“request”、“session”、“globalsession”三種web作用域;Spring還允許用戶定制自己的作用域。
作用域 | 描述 |
---|---|
singleton |
在每個Spring IoC容器中一個bean定義對應一個對象實例。 (默認)在spring IOC容器中僅存在一個Bean實例,Bean以單實例的方式存在。 |
prototype |
一個bean定義對應多個對象實例。 每次從容器中調用Bean時,都返回一個新的實例,即每次調用getBean()時,相當於執行new XxxBean()的操作。 |
request |
在一次HTTP請求中,一個bean定義對應一個實例;即每次HTTP請求將會有各自的bean實例,它們依據某個bean定義創建而成。該作用域僅在基於web的Spring |
session |
在一個HTTP 同一個HTTP session共享一個Bean,不同HTTP session使用不同的Bean,該作用域僅適用於webApplicationContext環境。 |
globalSession |
在一個全局的HTTP |
1.singleton
“singleton”作用域的Bean只會在每個Spring IoC容器中存在一個實例,而且其完整生命周期完全由Spring容器管理。對於所有獲取該Bean的操作Spring容器將只返回同一個Bean。註意:spring將Bean的默認作用域定為singleton。
當一個bean的作用域設置為singleton, 那麽Spring IOC容器中只會存在一個共享的bean實例,並且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。換言之,當把 一個bean定義設置為singleton作用域時,Spring IOC容器只會創建該bean定義的唯一實例。這個單一實例會被存儲到單例緩存(singleton cache)中,並且所有針對該bean的後續請求和引用都將返回被緩存的對象實例,這裏要註意的是singleton作用域和GOF設計模式中的單例是完全不同的,單例設計模式表示一個ClassLoader中只有一個class存在,而這裏的singleton則表示一個容器對應一個bean,也就是說當一個bean被標識為singleton時候,spring的IOC容器中只會存在一個該bean。
在默認情況下,spring的ApplicationContext容器在啟動時,自動實例化所有singleton的Bean並緩存於容器中。
雖然啟動時會花費一些時間,但帶來兩個好處:
(1)首先對Bean提前的實例化操作會及早發現一些潛在的配置問題。
(2)其次Bean以緩存的方式保存,當運行時使用到該Bean時就無須再實例化了,加快了運行效率。
如果用戶不希望在容器啟動時提前實例化singleton的Bean,可以通過lazy-init屬性進行控制。但是lazy-init="true"的Bean在某些情況下依舊會提前實例化:如果該Bean被其它需要提前實例化的Bean引用到,spring也將忽略延遲實例化的設置。
2.prototype
即原型,指每次向Spring容器請求獲取Bean都返回一個全新的Bean,相對於"singleton"來說就是不緩存Bean,每次都是一個根據Bean定義創建的全新Bean。
當使用prorotype作為作用域時,Bean會導致每次對該Bean的請求都創建一個Bean實例,所以對有狀態的Bean應該使用prorotype作用域,無狀態Bean 則使用singleton作用域。還有就是Spring不能對一個prototype作用域 bean的整個生命周期負責,容器在初始化、配置、裝飾或者是裝配完一個prototype實例後,將它交給客戶端,隨後就對該prototype實例不聞不問了。
在默認情況下,spring容器在啟動時不實例化prototype的Bean。此外,spring容器將prototype的Bean交給調用者後,就不再管理它的生命周期。
下面測試一下singleton與prototype,Java類用之前建的HelloWorld.java。
package com.spring.test; public class HelloWorld { private String info; public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } public HelloWorld(){ System.out.println("執行構造函數!"); } }
配置文件applicationContext.xml:
<bean id="hello" class="com.spring.test.HelloWorld"> <property name="info" value="Hello,This is my first Spring Application!"></property> </bean>
其中id為"hello"的bean聲明為singleton(因為默認是singleton,所以可以不顯示指定)
測試方法如下:
/** * 測試Bean的作用域 */ @Test public void test(){ //讀取配置文件 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //獲取bean的實例 HelloWorld t=(HelloWorld) ctx.getBean("hello"); HelloWorld t1=(HelloWorld) ctx.getBean("hello"); System.out.println(t==t1); }
執行結果為:
可以看到只打印了一次“執行構造函數!”,並且t=t1,說明它們是同一對象。
修改配置文件,將id為"hello"的bean的scope屬性改為"prototype"。
<bean id="hello" class="com.spring.test.HelloWorld" scope="prototype"> <property name="info" value="Hello,This is my first Spring Application!"></property> </bean>
再次執行上面的測試方法,結果如下:
打印了兩次“執行構造函數!”,並且t!=t1。
3.web應用中的作用域
在Web應用中,我們可能需要將數據存儲到request、session、global session。因此Spring提供了三種Web作用域:request、session、globalSession。
(1)request作用域:request表示針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效。示例:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
針對每次HTTP請求,Spring容器會根據loginAction bean定義創建一個全新的LoginAction bean實例, 且該loginAction bean實例僅在當前HTTP request內有效,因此可以根據需要放心的更改所建實例的內部狀態, 而其他請求中根據loginAction bean定義創建的實例,將不會看到這些特定於某個請求的狀態變化。 當處理請求結束,request作用域的bean實例將被銷毀。
(2)session作用域:針對每個會話,spring容器都會創建一個全新的Bean,且該Bean僅在當前HTTP Session內有效。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
針對某個HTTP Session
,Spring容器會根據userPreferences
bean定義創建一個全新的userPreferences
bean實例, 且該userPreferences
bean僅在當前HTTP Session
內有效。 與request作用域
一樣,你可以根據需要放心的更改所創建實例的內部狀態,而別的HTTP Session
中根據userPreferences
創建的實例,將不會看到這些特定於某個HTTP Session
的狀態變化。 當HTTP Session
最終被廢棄的時候,在該HTTP Session
作用域內的bean也會被廢棄掉。
(3)globalSession作用域:類似於session作用域,只是其用於portlet環境的web應用。如果在非portlet環境將視為session作用域。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
global session
作用域類似於標準的HTTP Session
作用域,不過它僅僅在基於portlet的web應用中才有意義。Portlet規範定義了全局Session
的概念,它被所有構成某個portlet web應用的各種不同的portlet所共享。在global session
作用域中定義的bean被限定於全局portlet Session
的生命周期範圍內。
請註意,假如你在編寫一個標準的基於Servlet的web應用,並且定義了一個或多個具有global session
作用域的bean,系統會使用標準的HTTP Session
作用域,並且不會引起任何錯誤。
Spring系列之bean的使用