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

IoC容器和Bean簡介

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

org.springframework.beansorg.springframework.context包是Spring框架IoC容器的基礎(chǔ)。BeanFactory接口提供了一種先進的配置機制能夠管理任何類型的對象。ApplicationContextBeanFactory的子接口。它增加了一些跟Spring AOP特性更為簡單的集成,包括信息資源處理(國際化使用),事件發(fā)表,以及應(yīng)用層特別上下文的使用。

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

在Spring中,那些在你應(yīng)用中,由Spring IoC容器管理的骨干對象,都叫做Bean。Bean就是一個由Spring IoC容器實例化,裝載,以及管理的對象。Bean也是你應(yīng)用中的對象。Bean以及Bean的那些依賴對象,都是通過容器使用的元數(shù)據(jù)反射成的。

容器概覽

接口org.springframework.context.ApplicationContext表示Spring IoC容器同時負(fù)責(zé)實例化,配置,以及裝載前面提及的Bean對象。容器通過讀取配置元數(shù)據(jù)來知道那些對象需要實例化,配置以及裝載。配置元數(shù)據(jù)可以寫到XML中,Java注解中,或者Java代碼中。

幾種不同的由Spring針對ApplicationContext接口的實現(xiàn)都是可以直接使用的。在單機環(huán)境中,使用ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext也是非常常見的。盡管XML是傳統(tǒng)的定義元數(shù)據(jù)的格式,你也可以通過Java注解或者代碼來提供額外的元數(shù)據(jù)。

在大多數(shù)應(yīng)用場景中,用戶代碼不需要實例化Spring IoC容器。比如,在Web應(yīng)用場景下,只需要在web.xml中添加少數(shù)幾行代碼就可以由Web容器來創(chuàng)建Spring IoC容器。

下面的圖是一個high-level的Spring工作圖。你的應(yīng)用的類以及配置當(dāng)中的元數(shù)據(jù)只有在ApplicationContext創(chuàng)建了,初始化好,你才有一個完全配置好的,可執(zhí)行的系統(tǒng)或應(yīng)用。

圖6.1 Spring IoC 容器

圖6.1 Spring IoC 容器

配置元數(shù)據(jù)

如前面的圖所表現(xiàn)的,Spring IoC容器會使用配置元數(shù)據(jù)。這個數(shù)據(jù)也表示了你希望Spring容器在應(yīng)用中如何來實例化,配置,以及裝載對象。

配置元數(shù)據(jù)傳統(tǒng)的提供方式是使用簡單直觀的XML格式,當(dāng)然也是本章所用來表達(dá)Spring IoC容器的一些關(guān)鍵的概念和特性的格式。

基于XML格式的元數(shù)據(jù)配置不是唯一的配置元數(shù)據(jù)的方式。Spring IoC容器本身和使用哪一種元數(shù)據(jù)來寫入配置是完全解耦的。目前很多開發(fā)者也選擇使用基于Java的方式來配置Spring應(yīng)用。

關(guān)于使用其他不同形式的元數(shù)據(jù),可以參考

  • 基于注解的配置: Spring 2.5 支持基于注解的元數(shù)據(jù)配置

  • 基于Java的配置: Spring 3.0 以后,Spring JavaConfig項目成為了Spring 框架的一部分。 開發(fā)者可以通過定義Java類來定義Bean。如果想使用這些新特性,參考@Configuration,@Bean,@Import以及@DependsOn注解。

Spring 配置包括至少一種Bean的定義方式。基于XML配置元數(shù)據(jù)都是通過配置< bean/>這樣的標(biāo)簽,在最高級別的< beans/>標(biāo)簽之下。也可以通過Java 配置使用 @Bean注解的方法到使用@Configuration注解的類上面。

這些Bean定義所關(guān)聯(lián)的實際的對象構(gòu)成了你的應(yīng)用。通常,你可以定義服務(wù)層對象,數(shù)據(jù)接入層對象(Dao),表現(xiàn)層對象比如Struts里面的Action實例,基礎(chǔ)構(gòu)成對象比如Hibernate的SessionFactories,JMSQueues等等。通常不配置細(xì)粒度的域?qū)ο蟮娜萜?因為它通常是DAOs的責(zé)任和業(yè)務(wù)邏輯創(chuàng)建和加載域?qū)ο蟆H欢?,你可以使用Spring和AspectJ集成來配置在IoC容器控制之外的對象??梢詤⒖嘉恼?a target="_blank"> Using AspectJ to dependency-inject domain objects with Spring.

。

下面的例子展示了基于XML的配置元數(shù)據(jù)的基本結(jié)構(gòu)


<?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屬性是一個字符串,是用來區(qū)分獨立的Bean定義的。class屬性定義了Bean使用的類型,用的是全名。id的值用來讓Bean對象之間相互引用。

實例化容器

實例化Spring IoC容器很直接。ApplicationContext的構(gòu)造函數(shù)可以通過一些資源地址的字符串來讓容器從中加載配置元數(shù)據(jù)。這些文件可以來自本地文件系統(tǒng),或者Java的CLASSPATH等。


ApplicationContext context =

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

如下的例子展示了一個服務(wù)層對象(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>

如下的例子展示了一個數(shù)據(jù)接入層對象(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>

在之前的例子中,服務(wù)層對象包括了類PetStoreServiceImpl,并且兩個數(shù)據(jù)接入對象類型分別是JpaAccountDao以及JpaItemDao(基于JPA O/R mapping 標(biāo)準(zhǔn))。property name 元素指的是Bean屬性的名字,ref 指的是另一個定義的bean。這種id和ref元素的關(guān)聯(lián),表示了不同對象之間的依賴關(guān)系。

組合基于XML的元數(shù)據(jù)配置

有時將bean定義到多個XML文件更清晰一些。通常來說,在開發(fā)者的架構(gòu)中一個單獨的XML配置文件代表一個單獨的邏輯層,或者單獨的模塊。

開發(fā)者可以使用應(yīng)用上下文的構(gòu)造函數(shù)來加載這些包含bean的XML。這個構(gòu)造函數(shù)可以使用多個資源路徑,比如之前一節(jié)中展示的那樣。或者可以使用一個或者多個< import/>標(biāo)簽來加載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來加載。所有路徑都是相對于當(dāng)前文件的所在的路徑。所以services.xml必須和當(dāng)前文件在同一個路徑或classpath路徑。而messageSource.xml和themeSource.xml必須定義在resources路徑下。如上所述,第一個斜線是被忽視掉的,考慮到這些路徑都是相對的,最好不要使用第一個下線。這些文件的內(nèi)容是被引用的,包括最高級的< beans/>元素,所以這些文件針對Spring Bean的XML定義必須有效。

很可能,通過使用相對路徑"../"來獲得引用的文件,但是并不推薦這樣做。這樣做會創(chuàng)建一個針對當(dāng)前應(yīng)用的外部依賴。尤其是這個路徑中包含“classpath”,注入這樣的URL(比如,“classpath:../services.xml”),這樣會引用一個運行時的classpath根目錄,然后在查找其父目錄。Classpath配置的改變可能會變成一個完全不一樣的目錄。

當(dāng)然,你也可以使用一些完整的路徑而不使用相對路徑,比如像“file:C:/config/service.xml”或者“classpath:/config/services.xml”。然而,一定要注意這樣做你是在耦合你的應(yīng)用到你本地的絕對路徑上。通常,更好的方式是使用引用來針對這些絕對路徑,比如“${...}”這類占位符,JVM系統(tǒng)是可以在運行時解析的。

使用容器

ApplicationContext是一個負(fù)責(zé)注冊不同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();

開發(fā)者可以使用getBean()來獲得Bean對象。ApplicationContext接口有幾個方法來獲取Bean實例,但是開發(fā)者的代碼中可能用不到這些方法。事實上,開發(fā)者的應(yīng)用不該調(diào)用getBean()方法,且不依賴于Spring的API。比如,Spring與Web框架的集成,為多種框架的控制層等提供了依賴注入。

Bean 概述

Spring IoC容器管理了很多的Bean對象。這些Bean對象都是根據(jù)容器的配置元數(shù)據(jù)所創(chuàng)建的,比如基于XML的<bean/>定義。

在容器里面,Bean的定義都被表現(xiàn)為BeanDefinition對象,包含以下元數(shù)據(jù):

  • package-qualified類名,通常就是實際實現(xiàn)Bean接口的類。

  • Bean的行為配置元素,也就是那些Bean在容器里應(yīng)有的狀態(tài)(范圍,生命周期回調(diào)等等)

  • 引用到的其他Bean所必須的一些那些Bean配置。這些引用也稱為依賴

  • 其他用來創(chuàng)建對象的一些配置,比如,Bean中引用用來管理連接池的連接數(shù)字,或者連接池的上限等。

除了使用實現(xiàn)定義的元數(shù)據(jù)來創(chuàng)建Bean,ApplicationContext的實現(xiàn)也允許開發(fā)者將已存在的容器外的對象注冊為Bean對象??梢酝ㄟ^進去ApplicationContext的BeanFactory中的getBeanFactory()方法來獲得在DefaultListableBeanFactory中實現(xiàn)的BeanFactory。DefaultListableBeanFactory通過registerSingleton(..)以及registerBeanDefinition(..)支持前面的操作。然而,通常情況下,應(yīng)用都只是使用元數(shù)據(jù)中定義的Bean對象。

Bean的元數(shù)據(jù)以及手工支持的單例的最好盡早注冊到Spring容器中,防止容器在裝載這些Bean的過程中發(fā)生錯誤。然而,覆蓋掉已存在的元數(shù)據(jù)和存在的單例Bean也是支持的,但是在運行時注冊Bean有在官方上并不支持,而且因為Bean狀態(tài)的不一致導(dǎo)致并發(fā)異常。

命名Bean

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

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

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

Bean的命名習(xí)慣

一般習(xí)慣就是根據(jù)Java的field變量的方式來命名。以小寫開始的駝峰命名。比如accountManager,userDao,loginController等。

一致的命名方式可以讓開發(fā)者配置更加簡單易懂,而且如果你使用Spring AOP的話,這樣做也很有益處。

在Bean定義之外增加別名

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

然而,在Bean定義的時候來特指別名有的時候是不夠的。有的時候引用別名來定義在其他的地方能夠更清晰。這也是大系統(tǒng)的一些常見場景,根據(jù)不同的子系統(tǒng)來區(qū)分配置信息,每個子系統(tǒng)都有自己的定義。在基于XML的配置元數(shù)據(jù)中,可以使用<alias/>元素來做到。


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

這種情況下,在仙童的容器中,所有name是fromName的Bean,也能通過alias定義來通過toName來引用。

舉例來說,子系統(tǒng)A的元數(shù)據(jù)可能會通過一個subsystemA-dataSource來引用其數(shù)據(jù)源。而子系統(tǒng)B的配置元數(shù)據(jù)可能通過subsystemB-dataSource來引用數(shù)據(jù)源。當(dāng)組合成一個應(yīng)用時,會同時使用這兩個子系統(tǒng)通過myApp-dataSource來引用數(shù)據(jù)源。如果希望通過3個名字來指向一個對象,你可以通過應(yīng)用的配置元數(shù)據(jù)配置如下定義。


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

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

現(xiàn)在每個組件和主應(yīng)用程序可以通過名稱引用數(shù)據(jù)源是獨一無二的,保證不與其他任何沖突定義(有效地創(chuàng)建一個名稱空間),然而他們引用同一個bean。

實例化Bean

Bean定義的本身其實也是創(chuàng)建Bean對象的菜譜,容器通過這個定義來的元數(shù)據(jù)來將創(chuàng)建實際的Bean對象。

如果開發(fā)者使用的是基于XML的配置元數(shù)據(jù),開發(fā)者可以通過特指Bean的class字段來確定Bean被實例化成指定的對象。class屬性通常來說,在Bean定義中是必須的。開發(fā)者可以通過如下方式使用Class屬性:

  • 通常,指定Bean的class屬性,可以讓容器直接通過Bean定義的類的夠早函數(shù)來直接構(gòu)成,某種程度上來說,也就是調(diào)用Java的new操作符。

  • 也可以特指某個靜態(tài)的工廠方法來創(chuàng)建對象,當(dāng)然只有少數(shù)情況需要由容器調(diào)用靜態(tài)的工廠方法來創(chuàng)建Bean對象。靜態(tài)工廠方法所返回的對象類型可是就是這個類本身,也可以是其他的類。

內(nèi)部類。如果開發(fā)者想通過配置一個Bean為靜態(tài)內(nèi)部類,開發(fā)者需要指定二進制的嵌套類的類名。

舉例來說,比如有一個類名為Foo在包com.example包之中,而且這個Foo類其中有一個靜態(tài)的嵌套類叫做Bar,那么如果想使用Bar來作為Bean的話,它的class屬性需要為

com.example.Foo$Bar

需要注意的是,$符號就是用來區(qū)外部類和內(nèi)部類的。

通過構(gòu)造函數(shù)實例化

當(dāng)開發(fā)者通過構(gòu)造函數(shù)來創(chuàng)建Bean對象的時候,所有的普通的類都能夠和Spring協(xié)同工作。也就是說,一般的作為Bean的類是不需要實現(xiàn)一些特殊的接口的。僅僅指定Bean的類就足夠了。然而,根據(jù)你使用IoC容器的不同,開發(fā)者可能需要配置默認(rèn)(無參)構(gòu)造函數(shù)。

Spring IoC容器可以幫你管理任何你想要管理的類。并不僅限于Bean對象。大多數(shù)的Spring開發(fā)者更多在容器中使用Bean對象配合getter,setter方法以及無參的構(gòu)造函數(shù)。當(dāng)然,開發(fā)者也可以在容器中管理一些非Bean樣式的對象。比如說,一個不被引用的連接池,Spring仍然可以管理它。

使用基于XML的元數(shù)據(jù)配置方式,Bean的配置可以如下:


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

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

通過靜態(tài)工廠方法實例化

當(dāng)通過靜態(tài)工廠方法來定義Bean對象的時候,開發(fā)者可以使用class屬性來指定包含工廠方法的類,通過factory-method來指定生成Bean的方法。開發(fā)者可以調(diào)用這個方法,并返回一個對象。

下面的Bean定義,就是通過調(diào)用工廠方法所創(chuàng)建的。定義不會指定方法返回的對象的類型,而是包含了工廠方法的類。在如下的例子中createInstance()方法必須為靜態(tài)方法。


<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;

    }

}

通過實例工廠方法實例化

比較類似前面提到的靜態(tài)工廠方法,不同的是,這次是通過調(diào)用非靜態(tài)的實例方法來創(chuàng)建一個新的Bean對象的。如果想使用這種機制,需要將Bean的class屬性置空,而是用factory-bean屬性,特指容器中包含的那個包含創(chuàng)建該Bean實例方法的那個Bean。同時將這個Bean的factory-method屬性為實際的調(diào)用方法。


<!-- 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;

    }

}

當(dāng)然,一個工廠類也可以擁有多余一個工廠方法。


<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容器中配置的專門通過實例方法或者靜態(tài)方法來創(chuàng)建Bean的一個Bean。相對而言,FactoryBean指的是Spring一種特指的FactoryBean.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容