1. 程式人生 > >Spring核心技術(一)——IoC容器和Bean簡介

Spring核心技術(一)——IoC容器和Bean簡介

IoC容器和Bean簡介

這章包括了Spring框架對於IoC規則的實現。Ioc也同DI(依賴注入)。而物件是通過建構函式,工廠方法,或者一些Set方法來定義物件之間的依賴的。容器在建立這些Bean物件的時候同時就會注入這些依賴。這個過程是根本上的反轉了,不再由Bean本身來控制例項化和定位依賴,而是通過服務定位來控制這個過程,也是IoC(控制反轉)的由來。

org.springframework.beansorg.springframework.context包是Spring框架IoC容器的基礎。BeanFactory介面提供了一種先進的配置機制能夠管理任何型別的物件。ApplicationContext

BeanFactory的子介面。它增加了一些跟Spring AOP特性更為簡單的整合,包括資訊資源處理(國際化使用),事件發表,以及應用層特別上下文的使用。

簡而言之,BeanFactory提供了框架配置和基本的功能,而ApplicationContext提供了更多企業級特性。ApplicationContextBeanFactory的超集,而且在這章Spring IoC容器中唯一使用的。想要更多的瞭解BeanFactory的話,請參考6.16。

在Spring中,那些在你應用中,由Spring IoC容器管理的骨幹物件,都叫做Bean。Bean就是一個由Spring IoC容器例項化,裝載,以及管理的物件。Bean也是你應用中的物件。Bean以及Bean的那些依賴物件,都是通過容器使用的元資料反射成的。

容器概覽

介面org.springframework.context.ApplicationContext表示Spring IoC容器同時負責例項化,配置,以及裝載前面提及的Bean物件。容器通過讀取配置元資料來知道那些物件需要例項化,配置以及裝載。配置元資料可以寫到XML中,Java註解中,或者Java程式碼中。

幾種不同的由Spring針對ApplicationContext介面的實現都是可以直接使用的。在單機環境中,使用ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext也是非常常見的。儘管XML是傳統的定義元資料的格式,你也可以通過Java註解或者程式碼來提供額外的元資料。

在大多數應用場景中,使用者程式碼不需要例項化Spring IoC容器。比如,在Web應用場景下,只需要在web.xml中新增少數幾行程式碼就可以由Web容器來建立Spring IoC容器。

下面的圖是一個high-level的Spring工作圖。你的應用的類以及配置當中的元資料只有在ApplicationContext建立了,初始化好,你才有一個完全配置好的,可執行的系統或應用。

圖6.1 Spring IoC 容器

圖6.1 Spring IoC 容器

配置元資料

如前面的圖所表現的,Spring IoC容器會使用配置元資料。這個資料也表示了你希望Spring容器在應用中如何來例項化,配置,以及裝載物件。

配置元資料傳統的提供方式是使用簡單直觀的XML格式,當然也是本章所用來表達Spring IoC容器的一些關鍵的概念和特性的格式。

基於XML格式的元資料配置不是唯一的配置元資料的方式。Spring IoC容器本身和使用哪一種元資料來寫入配置是完全解耦的。目前很多開發者也選擇使用基於Java的方式來配置Spring應用。

關於使用其他不同形式的元資料,可以參考

  • 基於註解的配置: Spring 2.5 支援基於註解的元資料配置
  • 基於Java的配置: Spring 3.0 以後,Spring JavaConfig專案成為了Spring 框架的一部分。 開發者可以通過定義Java類來定義Bean。如果想使用這些新特性,參考@Configuration,@Bean,@Import以及@DependsOn註解。

Spring 配置包括至少一種Bean的定義方式。基於XML配置元資料都是通過配置< bean/>這樣的標籤,在最高級別的< beans/>標籤之下。也可以通過Java 配置使用 @Bean註解的方法到使用@Configuration註解的類上面。

這些Bean定義所關聯的實際的物件構成了你的應用。通常,你可以定義服務層物件,資料接入層物件(Dao),表現層物件比如Struts裡面的Action例項,基礎構成物件比如Hibernate的SessionFactories,JMSQueues等等。通常不配置細粒度的域物件的容器,因為它通常是DAOs的責任和業務邏輯建立和載入域物件。然而,你可以使用Spring和AspectJ整合來配置在IoC容器控制之外的物件。可以參考文章 Using AspectJ to dependency-inject domain objects with Spring.

下面的例子展示了基於XML的配置元資料的基本結構

<?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">

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

id屬性是一個字串,是用來區分獨立的Bean定義的。class屬性定義了Bean使用的型別,用的是全名。id的值用來讓Bean物件之間相互引用。

例項化容器

例項化Spring IoC容器很直接。ApplicationContext的建構函式可以通過一些資源地址的字串來讓容器從中載入配置元資料。這些檔案可以來自本地檔案系統,或者Java的CLASSPATH等。

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

如下的例子展示了一個服務層物件(services.xml)的配置檔案。

<?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">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

如下的例子展示了一個數據接入層物件(daos.xml)檔案

<?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">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

在之前的例子中,服務層物件包括了類PetStoreServiceImpl,並且兩個資料接入物件型別分別是JpaAccountDao以及JpaItemDao(基於JPA O/R mapping 標準)。property name 元素指的是Bean屬性的名字,ref 指的是另一個定義的bean。這種id和ref元素的關聯,表示了不同物件之間的依賴關係。

組合基於XML的元資料配置

有時將bean定義到多個XML檔案更清晰一些。通常來說,在開發者的架構中一個單獨的XML配置檔案代表一個單獨的邏輯層,或者單獨的模組。

開發者可以使用應用上下文的建構函式來載入這些包含bean的XML。這個建構函式可以使用多個資源路徑,比如之前一節中展示的那樣。或者可以使用一個或者多個< import/>標籤來載入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>

在上述例子中,外部的Bean定義通過3個檔案:services.xml,messageSource.xml以及themeSource.xml來載入。所有路徑都是相對於當前檔案的所在的路徑。所以services.xml必須和當前檔案在同一個路徑或classpath路徑。而messageSource.xml和themeSource.xml必須定義在resources路徑下。如上所述,第一個斜線是被忽視掉的,考慮到這些路徑都是相對的,最好不要使用第一個下線。這些檔案的內容是被引用的,包括最高階的< beans/>元素,所以這些檔案針對Spring Bean的XML定義必須有效。

很可能,通過使用相對路徑”../”來獲得引用的檔案,但是並不推薦這樣做。這樣做會建立一個針對當前應用的外部依賴。尤其是這個路徑中包含“classpath”,注入這樣的URL(比如,“classpath:../services.xml”),這樣會引用一個執行時的classpath根目錄,然後在查詢其父目錄。Classpath配置的改變可能會變成一個完全不一樣的目錄。
當然,你也可以使用一些完整的路徑而不使用相對路徑,比如像“file:C:/config/service.xml”或者“classpath:/config/services.xml”。然而,一定要注意這樣做你是在耦合你的應用到你本地的絕對路徑上。通常,更好的方式是使用引用來針對這些絕對路徑,比如“${…}”這類佔位符,JVM系統是可以在執行時解析的。

使用容器

ApplicationContext是一個負責註冊不同Bean的工廠介面。可以通過T getBean(String name, Class<T> requiredType)方法獲取Bean的例項。
獲取的程式碼如下:

// create and configure beans
ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

開發者可以使用getBean()來獲得Bean物件。ApplicationContext介面有幾個方法來獲取Bean例項,但是開發者的程式碼中可能用不到這些方法。事實上,開發者的應用不該呼叫getBean()方法,且不依賴於Spring的API。比如,Spring與Web框架的整合,為多種框架的控制層等提供了依賴注入。

Bean 概述

Spring IoC容器管理了很多的Bean物件。這些Bean物件都是根據容器的配置元資料所建立的,比如基於XML的<bean/>定義。
在容器裡面,Bean的定義都被表現為BeanDefinition物件,包含以下元資料:

  • package-qualified類名,通常就是實際實現Bean介面的類。
  • Bean的行為配置元素,也就是那些Bean在容器裡應有的狀態(範圍,生命週期回撥等等)
  • 引用到的其他Bean所必須的一些那些Bean配置。這些引用也稱為依賴
  • 其他用來建立物件的一些配置,比如,Bean中引用用來管理連線池的連線數字,或者連線池的上限等。

除了使用實現定義的元資料來建立Bean,ApplicationContext的實現也允許開發者將已存在的容器外的物件註冊為Bean物件。可以通過進去ApplicationContext的BeanFactory中的getBeanFactory()方法來獲得在DefaultListableBeanFactory中實現的BeanFactory。DefaultListableBeanFactory通過registerSingleton(..)以及registerBeanDefinition(..)支援前面的操作。然而,通常情況下,應用都只是使用元資料中定義的Bean物件。

Bean的元資料以及手工支援的單例的最好儘早註冊到Spring容器中,防止容器在裝載這些Bean的過程中發生錯誤。然而,覆蓋掉已存在的元資料和存在的單例Bean也是支援的,但是在執行時註冊Bean有在官方上並不支援,而且因為Bean狀態的不一致導致併發異常。

命名Bean

每一個Bean都有不止一個區分符。這些區分符必須在這個容器中唯一。通常,一個Bean只有一個區分符,但是如果多餘一個,那麼額外的區分符也作為這個Bean的別名。

在基於XML配置的元資料中,你可以使用id或者name屬性來作為Bean的區分符。id屬性允許你特指唯一的一個id。方便起見,這個名字都是有字元跟數字的(‘myBean’, ‘fooService’等),也可以包含特殊的字元。如果你也通過其他別名來使用Bean,開發者也可以給Bean使用name屬性,以,,;或者空格來區分。由於歷史的原因,在Spring 3.1之前,id屬性是被定義成一種xsd:ID型別的。在3.1中,id的型別還被定義成xsd:string型別。

開發者也可以不給Bean定義id或者name。如果Bean沒有名字或者id的話,容器會幫助Bean生成一個獨特的名字。但是如果你想使用ref這樣的元素來定位到Bean的話,你還是需要新增一個名字的。不使用名字主要是為了使用內在的Bean以及聯合裝載。

Bean的命名習慣
一般習慣就是根據Java的field變數的方式來命名。以小寫開始的駝峰命名。比如accountManager,userDao,loginController等。
一致的命名方式可以讓開發者配置更加簡單易懂,而且如果你使用Spring AOP的話,這樣做也很有益處。

在Bean定義之外增加別名

在Bean定義本身,開發者通過使用id屬性以及name屬性可以為Bean定義多個名字。這些名字也同樣能指向相同的Bean物件,在一些場景下是很實用的。比如允許元件引用多個依賴的話,通過名字會更有效。

然而,在Bean定義的時候來特指別名有的時候是不夠的。有的時候引用別名來定義在其他的地方能夠更清晰。這也是大系統的一些常見場景,根據不同的子系統來區分配置資訊,每個子系統都有自己的定義。在基於XML的配置元資料中,可以使用<alias/>元素來做到。

<alias name="fromName" alias="toName"/>

這種情況下,在仙童的容器中,所有name是fromName的Bean,也能通過alias定義來通過toName來引用。
舉例來說,子系統A的元資料可能會通過一個subsystemA-dataSource來引用其資料來源。而子系統B的配置元資料可能通過subsystemB-dataSource來引用資料來源。當組合成一個應用時,會同時使用這兩個子系統通過myApp-dataSource來引用資料來源。如果希望通過3個名字來指向一個物件,你可以通過應用的配置元資料配置如下定義。

<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />

現在每個元件和主應用程式可以通過名稱引用資料來源是獨一無二的,保證不與其他任何衝突定義(有效地建立一個名稱空間),然而他們引用同一個bean。

例項化Bean

Bean定義的本身其實也是建立Bean物件的菜譜,容器通過這個定義來的元資料來將建立實際的Bean物件。

如果開發者使用的是基於XML的配置元資料,開發者可以通過特指Bean的class欄位來確定Bean被例項化成指定的物件。class屬性通常來說,在Bean定義中是必須的。開發者可以通過如下方式使用Class屬性:

  • 通常,指定Bean的class屬性,可以讓容器直接通過Bean定義的類的夠早函式來直接構成,某種程度上來說,也就是呼叫Java的new操作符。
  • 也可以特指某個靜態的工廠方法來建立物件,當然只有少數情況需要由容器呼叫靜態的工廠方法來建立Bean物件。靜態工廠方法所返回的物件型別可是就是這個類本身,也可以是其他的類。

內部類。如果開發者想通過配置一個Bean為靜態內部類,開發者需要指定二進位制的巢狀類的類名。
舉例來說,比如有一個類名為Foo在包com.example包之中,而且這個Foo類其中有一個靜態的巢狀類叫做Bar,那麼如果想使用Bar來作為Bean的話,它的class屬性需要為
com.example.Foo$Bar
需要注意的是,$符號就是用來區外部類和內部類的。

通過建構函式例項化
當開發者通過建構函式來建立Bean物件的時候,所有的普通的類都能夠和Spring協同工作。也就是說,一般的作為Bean的類是不需要實現一些特殊的介面的。僅僅指定Bean的類就足夠了。然而,根據你使用IoC容器的不同,開發者可能需要配置預設(無參)建構函式。

Spring IoC容器可以幫你管理任何你想要管理的類。並不僅限於Bean物件。大多數的Spring開發者更多在容器中使用Bean物件配合getter,setter方法以及無參的建構函式。當然,開發者也可以在容器中管理一些非Bean樣式的物件。比如說,一個不被引用的連線池,Spring仍然可以管理它。

使用基於XML的元資料配置方式,Bean的配置可以如下:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

通過靜態工廠方法例項化
當通過靜態工廠方法來定義Bean物件的時候,開發者可以使用class屬性來指定包含工廠方法的類,通過factory-method來指定生成Bean的方法。開發者可以呼叫這個方法,並返回一個物件。

下面的Bean定義,就是通過呼叫工廠方法所建立的。定義不會指定方法返回的物件的型別,而是包含了工廠方法的類。在如下的例子中createInstance()方法必須為靜態方法。

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

通過例項工廠方法例項化
比較類似前面提到的靜態工廠方法,不同的是,這次是通過呼叫非靜態的例項方法來建立一個新的Bean物件的。如果想使用這種機制,需要將Bean的class屬性置空,而是用factory-bean屬性,特指容器中包含的那個包含建立該Bean例項方法的那個Bean。同時將這個Bean的factory-method屬性為實際的呼叫方法。

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

當然,一個工廠類也可以擁有多餘一個工廠方法。

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private static AccountService accountService = new AccountServiceImpl();

    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }

}

這個方法顯示,工廠Bean本身也是可以通過依賴注入配置的。

在Spring文件中,工廠Bean指的是在Spring容器中配置的專門通過例項方法或者靜態方法來建立Bean的一個Bean。相對而言,FactoryBean指的是Spring一種特指的FactoryBean.