Spring in Action 學習筆記 第二章 裝配 Bean

 

一、容納你的 Bean

容器是 Spring 的核心。 Spring 使用 IoC 管理所有組成應用系統的組件。 Spring 有兩種不同的容器:

l          Bena 工廠( BeanFactory ):由 org.springframework.beans.factory.BeanFactory 接口定義。是最簡單的容器,提供了基礎的依賴注入支持。

l          應用上下文 (ApplicationContext) :由 org.springframework.context.ApplicationContext 接口定義。建立在 Bean 工廠之上提供了系統架構服務。

1 BeanFactory 介紹

       見名知意, BeanFactory 采用了工廠模式(抽象工廠模式 http://blog.csdn.net/qutr/archive/2006/01/22/586034.aspx 工廠方法模式 http://blog.csdn.net/qutr/archive/2006/07/21/954070.aspx )。這個類專門負責創建類(對象)。有些書上說 Spring BeanFactory 像一個超負荷運轉的機器,因為除了簡單的創建對象以外, BeanFactory 可以在實例化這些對象的時候,創建協作對象間的關聯關系。這樣就把配置的負擔從 Bean 自身以及 Bean 的調用者中脫離出來。更詳細的可以查看 BeanFactory 的源代碼。

Spring 中有幾種 BeanFactory 的實現。其中最長用的是 org.springframework.beans.factory.xml.XmlBeanFactory ,它根據 XML 文件中的定義裝載 Bean 。在 XmlBeanFactory 類中提供了兩個構造函數,通常我們用的是 XmlBeanFactory(Resource resource) throws BeansException 這個。他一般這樣定義: BeanFactory factory = new XmlBeanFactory(new FileSystemResource(“beans.xml”)); 其中傳遞的參數引用為: import org.springframework.core.io.FileSystemResource; 書中這里是不太正確的,因為在 Spring2.0 里已經不提供傳遞 java.io.InputStream 對象的構造函數了。翻譯者非常的不負責任,應該在這里給出說明。

BeanFactory 得到一個 Bean 只需調用 getBean(“beanName”) 方法,把你需要的 Bean 的名字當作參數傳遞進去就可以了。像這樣: MyBean myBean = (MyBean)factory.getBean(“myBean”); getBean() 方法被調用的時候,工廠就會開始實例化 Bean 并且使用依賴注入開始設置 Bean 的屬性。

事實上 BeanFactory 接口提供有 6 種方法供客戶代碼調用:

1 boolean containsBean(String) : 如果 BeanFactory 包含符合給定名稱的 Bean 定義或 Bean 實例,則返回 true

2 Object getBean(String) : 返回以給定名字注冊的 Bean 實例。就是上面提到的最簡單的一種。

3 Object getBean(String, Class) : 返回以給定名稱注冊的 Bean ,返回的 Bean 將被扔給( cast )給定的類。如果 Bean 不能被 Cast ,相應的異常( BeanNotOfRequiredTypeException )將被拋出。

4 Class getType(String name) : 返回給定名稱的 Bean Class 。如果沒有相應于給定名稱的 Bean ,則拋出 NoSuchBeanDefinitionException 異常。

5 boolean isSingleton(String) : 決定在給定名稱時, Bean 定義或 Bean 實例注冊是否為單件模式,如果相應于給定名稱的 Bean 不能被找到,則拋出 NoSuchBeanDefinitionException 異常。

6 String[] getAliases(String) : 如果在 Bean 的定義中有定義,則返回給定 Bean 名稱的別名。

public   interface  BeanFactory  {              //beanfactory的源碼


    String FACTORY_BEAN_PREFIX 
=   " & " ;



    Object getBean(String name) 
throws  BeansException;


    Object getBean(String name, Class requiredType) 
throws  BeansException;

    
    
boolean  containsBean(String name);

    
    
boolean  isSingleton(String name)  throws  NoSuchBeanDefinitionException;

        Class getType(String name) 
throws  NoSuchBeanDefinitionException;


    
    String[] getAliases(String name) 
throws  NoSuchBeanDefinitionException;

}

 

2 .使用應用上下文( ApplicationContext

使用 ApplicationContext 可以獲得 Sroing 框架的強大功能。表面上 ApplicationContext BeanFactory 差不多,但是 ApplicationContext 提供了更多的功能:

l          ApplicationContext 提供了文本信息解析工具,包括對國際化的支持。

l          ApplicationContext 提供了載入文件資源的通用方法,如載入圖片。

l          ApplicationContext 可以向注冊為監聽器的 Bean 發送事件。

l          ApplicationContext Spring2.0 里可能還加了其他功能。

有三種 ApplicationContext 的實現經常被用到:

l          ClassPathXmlApplicationContext— 從類路徑中的 XML 文件載入上下文定義信息,把上下文定義文件當成類路徑資源。(可以在整個類路徑中尋找 XML 文件)

l          FileSystemXmlApplicationContext— 從文件系統中的 XML 文件載入上下文定義信息。(只能在指定的路徑中尋找 XML 文件)

l          XmlWebApplicationContext— Web 系統中的 XMLwenjian 載入上下文定義信息。

其中 FileSystemXmlApplicationContext ClassPathXmlApplicationContext (他們都包含在 org.springframework.context.support 包下)的使用形式分別如下:

ApplicationContext context = new FileSystemXmlApplicationContext(“c:/foo.xml”);

ApplicationContext context2 = new ClassPathXmlApplicationContext(“foo.xml”)

應用上下文會在上下文啟動后預載入所有的單實例 Bean 。確保當你需要的時候他們已經準備好了你的應用不需要等待他們被創建。

總之實例化一個 Bean 大概有三種方法:

第一種:

Resource resource = new FileSystemResource("beans.xml");

BeanFactory factory = new XmlBeanFactory(resource);

第二種:

ClassPathResource resource = new ClassPathResource("beans.xml");

BeanFactory factory = new XmlBeanFactory(resource);

第三種:

ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml", "applicationContext-part2.xml"});

BeanFactory factory = (BeanFactory) context;

ApplicathionContext 的超類是 BeanFactory ,所以理所當然的這里的 context 可以是 BeanFactory

3 Bean 的生命周期

在書中作者用圖文并茂的方式解說了 Bean BeanFactory ApplicationContext 中的生命周期,從創建到銷毀的全過程。他們二者之間略有不同,書上講的比較詳細,這里就不羅列了。

 

二、基本裝配

Spring 容器內拼湊 Bean 叫做裝配。裝配 Bean 的時候,你是在告訴容器需要哪些 Bean 以及容器如何使用依賴注入將他們配合在一起。

1 .使用 XML 裝配

Bean 裝配在 Spring 中最常用的是 XML 文件。在前面提到過的 XmlBeanFactory ClassPathXmlApplicationContext FileSystemXmlApplicationContext XmlWebApplicationContext 都支持用 XML 裝配 Bean

Spring 規定了自己的 XML 文件格式,根元素為 <Beans> <Beans> 有多個 <Bean> 子元素。每個 <Bean> 定義了一個 Bean 如何被裝載到 Spring 容器中。看下面的名為 test.xml XML 文件:

<? xml version = "1.0" encoding = "UTF-8" ?>

<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >

< beans >

    < bean id = "foo"

          class = "springinaction.foo" >

    </ bean >

    < bean id = "bar"

          class  = "springinaction.bar" >

    </ bean >

</ beans >

Spring 中,對一個 Bean 的最基本的配置包括 Bean ID 和他的類全稱。在 test.xml 文件中其中 <Beans> 為根元素, bean id=”foo” 標明一個 Bean 他的 Id foo ,此處的 foo 是由程序員自己命名的,但是在一個大的系統中有好多個 Bean 這里要求 XML 中的 Id 要唯一,在程序代碼中如果想取得 foo 這個 Bean 就要用 getBean(“foo”) 了。 class=”springinaction.foo” Bean 的全稱類名。

2 .添加一個 Bean

Spring 中的 Bean 在缺省情況下是單實例模式(也叫單態模式, http://blog.csdn.net/qutr/archive/2006/02/28/611868.aspx )。如果想每次從容器中得到得到一個 Bean 的不同實例,需要將 Bean 定義為原型模式( http://blog.csdn.net/qutr/archive/2006/07/24/968297.aspx )。我們可以用 Bean singleton 屬性來設置,缺省情況為 true

< bean id  =   " foo "

          
class   =   " springinaction.foo "

          singleton
= " false " >

    Bean的定義中用init-method來初始化某些參數,用destroy-method來銷毀對象。

< bean id  =   " foo "

          
class   =   " springinaction.foo "

          singleton
= " false "

          init
- method = " init "

          destroy
- method = " destory " >

    Spring也提供了兩個接口來實現相同的功能:InitializingBeanDisposableBean

3 .通過 Set 方法注入依賴。

寫過 JavaBean 的人可能都熟悉 get set 方法。 Spring 就是用 Set 注入來配置 Bean 的。 <bean> 的子元素 <property> 指明了使用它們的 set 方法來注入。你可以注入任何類型。

每個 Bean (通常就是你定義的一個 class )通常都會有一些簡單的類型成員,如 int String 等等。通過使用 <property> 的子元素 <value> 可以設置基本類型的屬性(值)。

< bean id = "bar" class  = "springinaction.bar" >

        < property name = "barName" >

            < value > coffeeBar </ value >

        </ property >

</ bean >

如上所示,在我們的 springinaction.bar 類中定義了一個名為 barName String 類型的成員,用 < value > coffeeBar </ value > 中的coffeeBar來給他設置值。如:setName( barName ); 如果定義了一個int類型的成員我們可以在<value></value>中寫入一個數字。

利用property豐富的屬性我們可以為一個Bean引用其它Bean,通過<ref>實現;可以嵌入一個內部Bean,通常很少使用。可以裝配各種集合,如 java.util.List, java.util.Set, java.util.Map 等等。可以設置 properties null 值。

下面給出一個例子,這個例子同樣來自官方文檔。

< bean id = " moreComplexObject "   class = " example.ComplexObject " >

  
<!--  java.util.Properties  -->

  
< property name = " adminEmails " >

    
< props >

        
< prop key = " administrator " > administrator@somecompany.org </ prop >

        
< prop key = " support " > support@somecompany.org </ prop >

        
< prop key = " development " > development@somecompany.org </ prop >

    
</ props >

  
</ property >

  
<!--  java.util.List  -->

  
< property name = " someList " >

    
< list >

        
< value > a list element followed by a reference </ value >

        
< ref bean = " myDataSource "   />

    
</ list >

  
</ property >

  
<!--  java.util.Map  -->

  
< property name = " someMap " >

    
< map >

        
< entry >

            
< key >

                
< value > yup an entry </ value >

            
</ key >

            
< value > just some string </ value >

        
</ entry >

        
< entry >

            
< key >

                
< value > yup a ref </ value >

            
</ key >

            
< ref bean = " myDataSource "   />

        
</ entry >

    
</ map >

  
</ property >

  
<!--  java.util.Set  -->

  
< property name = " someSet " >

    
< set >

        
< value > just some string </ value >

        
< ref bean = " myDataSource "   />

    
</ set >

  
</ property >

</ bean >

一目了然,非常清楚就不多解釋了。關于集合Spring2.0又添加了新的內容,如:你如果使用的是JDK1.5那么還可以使用Java的泛型來清晰的解析各種容器所包含的類型,請參看:http://static.springframework.org/spring/docs/2.0.x/reference/beans.html#beans-collection-elements

4 .通過構造函數注入依賴

Set 注入是 Srping 所推薦的,但是 Set 注入的缺點是,他無法清晰的表示出哪些屬性是必須的,哪些是可選的。而使用構造函數注入的優勢是通過構造函數來強制以來關系,有了構造函數的約束不可能實現一個不完全或無法使用的 Bean

< bean id = " coo "   class = " springinaction.coo " >

        
< constructor - arg >

            
< value > cool </ value >

        
</ constructor - arg >

    
</ bean >

上面的例子通過構造函數傳值來實例化一個coo對象。如coo co = new coo(“cool”);

如果有多個構造函數,那么可以設置 < constructor-arg > typeindex來傳入參數了如果類型都一樣那么只能用index了,index的值是從0開始的。

Spring 為我們提供了Set注入和構造函數注入,在兩者的使用方式上個人有個人的理由和不同見解,在本書中作者給我們的建議大概是:看情況,那個合適用哪個(廢話 J )。但是, Spring 的開發團隊通常提倡人們使用 setter 注入。 其實Spring一共給我提供了三種注入方式:接口注入和上面的兩種注入。他們都有各自的優點,詳細的說明見《Spring開發指南》。

下面再給出兩個例子來說明一下Setter注入和constructor注入:

< bean id = " exampleBean "  

        
class = " examples.ExampleBean " >

    

     
<!--  用嵌套的  < ref />  元素進行setter注入  -->

    
< property name = " beanOne " >

        
< ref bean = " anotherExampleBean " />

    
</ property >

    

    
<!--  用嵌套的  ' ref '  屬性進行setter注入 (注意:如果你看得是翻譯的2.0的預覽版這里是錯誤的,請和官方的英文版對照看) -->

    
< property name = " beanTwo "  ref  =   " yetAnotherBean " /><! —我這里是正確的, 2 .0的中文預覽版這里寫錯了 -->

    

      
< property name = " integerProperty "  value = " 1 " />

    
</ bean >

 

    
< bean id = " anotherExampleBean "   class = " examples.AnotherBean " />

    
< bean id = " yetAnotherBean "   class = " examples.YetAnotherBean " />

Spring提供了快捷的ref屬性,來代替ref元素,但是使用ref屬性你要小!

下面是一個類

public   class  ExampleBean

{

 

    
private  AnotherBean beanOne;

    
private  YetAnotherBean beanTwo;

    
private   int  i;

 

    
public   void  setBeanOne(AnotherBean beanOne)  {

        
this .beanOne  =  beanOne;

    }


 

    
public   void  setBeanTwo(YetAnotherBean beanTwo)  {

        
this .beanTwo  =  beanTwo;

    }


 

    
public   void  setIntegerProperty( int  i)  {

        
this .i  =  i;

    }
    

}


上面是一個Bean(其實就是一個普通的不能在普通的Java類)和它相對應的XML的配置文件,是一個典型的setter注入。例子清晰易懂就不多說了。

< bean id = " exampleBean "  

        
class = " examples.ExampleBean " >

    

    
<!--  用嵌套的  < ref />  元素進行構造函數注入  -->

    
< constructor - arg >

        
< ref bean = " anotherExampleBean " />

    
</ constructor - arg >

    

    
<!--  用嵌套的  ' ref '  屬性進行構造函數注入  -->

    
< constructor - arg ref = " yetAnotherBean " />

      

    
< constructor - arg type = " int "  value = " 1 " />

    
</ bean >

    

    
< bean id = " anotherExampleBean "   class = " examples.AnotherBean " />

    
< bean id = " yetAnotherBean "   class = " examples.YetAnotherBean " />

下面是一個類

public   class  ExampleBean

{

    
private  AnotherBean beanOne;

    
private  YetAnotherBean beanTwo;

    
private   int  i;

    

    
public  ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean,  int  i)

    
{

        
this .beanOne  =  anotherBean;

        
this .beanTwo  =  yetAnotherBean;

        
this .i  =  i;

    }


}


上面是一個Bean和它相對應的XML的配置文件,是一個典型的constructor注入。例子也比較清晰易懂這里也就不多說了。

 

三、自動裝配

在前面講到的都是手動顯示裝配 Bean 的方法, Spring 還提供了自動裝配 Bean 的方法。只要設置 <Bean> autowire 屬性。有四種自動裝配類型:

l          byName :在容器中尋找和需要自動裝配的屬性名相同的 Bean (或 ID )。如果沒有找到這個屬性就沒有裝配上。

l          byType :在容器中尋找一個與自動裝配的屬性類型相同的 Bean 。如果沒有找到這個屬性就沒有裝配上。如果找到超過一個則拋出 org.springframework.beans.factory.UnsatisfiedDependencyException 異常。

l          constructor :在容器中查找與需要自動裝配的 Bean 的構造函數參數一致的一個或多個 Bean 。如果存在不確定 Bean 或構造函數容器會拋出 org.springframework.beans.factory.UnsatisfiedDependencyException 異常。

l          autodetect :首先嘗試使用 constuctor 來自動裝配,然后使用 byType 方式。不確定性的處理與 constructor 方式和 byType 方式一樣,都拋出 org.springframework.beans.factory.UnsatisfiedDependencyException 異常。

如下實例:

在前面的顯示裝配:

< bean id = " courseService "

        
class = " springinaction.chapter02.CourseServiceImpl " >

        
< property name = " courseDao " >

            
< ref bean = " courseDao " />

        
</ property >

        
< property name = " studentService " >

            
< ref bean = " studentService " />

        
</ property >

    
</ bean >

下面是自動裝配:

< bean id = " courseService "

        
class = " springinaction.chapter02.CourseServiceImpl "

        autowire
= " byName " />

SpringBean自動裝配的過程中很容易出現不確定性,這些不確定性會導致程序無法運行。那么在我們的實際應用中要避免出現裝配的不確定性。避免裝配的不確定性的最好的方法就是使用顯示裝配和自動裝配的混合方法。對有二義性的Bena使用顯示裝配,對沒有二義性的Bean使用自動裝配。

 

在通常情況下我們會分門別類的吧 Bena 設置在多個 XML 文件中。另外一種方法是使用一個或多個的 <import/> (就像我們寫 Java 程序要引入必要的包下的類一樣)元素來從另外一個或多個文件加載 Bean 定義。需要注意的是:任何 <import/> 元素必須放在配置文件中的 <bean/> 元素之前以完成 Bean 定義的導入。如下示例:

<beans>

<import resource="services.xml"/>

<import resource="resources/messageSource.xml"/>

<import resource="/resources/themeSource.xml"/>

<bean id="bean1" class="..."/>

<bean id="bean2" class="..."/>

<beans/>

上例中,三行 import 引入了必要的 XML 文件。兩行 bean 定義了 Bean 依照 XML Schema DTD ,被導入文件必須是完全有效的 XMLBean 定義文件,且包含上層的 <beans/> 元素。

 

四、使用 Spring 的特殊 Bean 和應用舉例

       在書中談到的 Spring 的特殊 Bean 的使用方法,我個人覺得這時 Spring IoC 的高級用法或者是邊角用法,由于我也是新手初學這里就不多啰嗦了,等以后在實踐中有了體會在補上,關于這方面的內容在 Spring2.0 的官方文檔上都有提到,這份文檔非常的詳細,我個人覺得只要把這份文檔好好看了其他的書甚至可以放在一邊了,當然 Rod 寫的書還是要好好看的。

       關于應用舉例,本書中在這章舉的例子不是很全面,剛開了個頭后面感覺就不說了。我本來想將我最近正做的一個實際應用中的例子放到這里,但是我的這個程序好像有點為了 Spring Spring 所以讓我改了又改,至今也沒有出來所以也就不再這里顯示了。等以后專門寫一篇文章來討論一下。

 

五、小結

       Spring 框架的核心是 Spring 容器。 BeanFactory 是最簡單的容器,他提供了最基本的依賴注入和 Bean 裝配服務。但是,畢竟 Spring 是一個輕量級框架,要想得到更多的高級框架服務時我們需要使用 ApplicationContext 容器,所以為了讓你的應用程序看起來更高級更上檔次盡量多的使用 ApplicationContext ,這也是本書作者所提倡的。

其實關于 Spring IoC 的核心和大部分內容在上面的文字中已經提到了,說來說去表面上就那些東西了。而 IoC DI )的真正思想是要靠大量的實踐去體會和領悟的。