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

IoC容器和Bean簡介

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

org.springframework.beansorg.springframework.context包是Spring框架IoC容器的基礎。BeanFactory接口提供了一種先進的配置機制能夠管理任何類型的對象。ApplicationContextBeanFactory的子接口。它增加了一些跟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.

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容