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.beans和org.springframework.context包是Spring框架IoC容器的基礎(chǔ)。BeanFactory接口提供了一種先進的配置機制能夠管理任何類型的對象。ApplicationContext是BeanFactory的子接口。它增加了一些跟Spring AOP特性更為簡單的集成,包括信息資源處理(國際化使用),事件發(fā)表,以及應(yīng)用層特別上下文的使用。
簡而言之,BeanFactory提供了框架配置和基本的功能,而ApplicationContext提供了更多企業(yè)級特性。ApplicationContext是BeanFactory的超集,而且在這章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 容器
配置元數(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.