內(nèi)容過長,core部分分開發(fā)布,core章節(jié)第二部分點擊:Spring Framework 官方文檔中文版—Core_part_2
主目錄或?qū)n}地址可以點擊:主目錄, 專題地址
朋友的博客, 內(nèi)容非常贊??, 想學(xué)更多的Java技術(shù), 可以去他那看看
優(yōu)秀的Java干貨博客
此部分的參考文檔,包含了Spring Framework的所有絕對重點內(nèi)容。
這節(jié)最重要的內(nèi)容就是Spring Framework的控制反轉(zhuǎn)(IoC)容器。徹底了解IoC容器之后,緊接著會對AOP技術(shù)進(jìn)行全面的講解。Spring Framework擁有自己的AOP框架,其在概念上很容易理解,并且成功解決了Java企業(yè)級編程中80%的AOP需求。
同時也介紹了Spring與AspectJ(最具特色,也是Java企業(yè)級應(yīng)用中最成熟的AOP實現(xiàn))的集成。
IoC容器
Spring IoC容器和bean的介紹
這節(jié)內(nèi)容主要介紹Spring Framework實現(xiàn)IoC的原理。IoC也被叫做依賴注入(DI)。這是一個對象定義他們的依賴的過程。其他對象使用它時,只能通過構(gòu)造方法參數(shù),傳遞給工廠方法的參數(shù),或者被設(shè)置到對象的屬性上并在此對象實例化后被構(gòu)建或者從一個工廠方法返回。容器在創(chuàng)建bean時注入這些依賴。這個過程是從根本上看是完全相反的,所以叫做控制反轉(zhuǎn)(IoC)。
此處硬生翻譯原文,估計會造成誤解,原文如下:This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) [1] principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes, or a mechanism such as the Service Locator pattern.
org.springframework.beans 和 org.springframework.context兩個包是Spring Framework的IoC容器基礎(chǔ)包。BeanFactory接口提供了高級的配置機(jī)制,可以管理任何類型的對象。ApplicationContext是BeanFactory的子接口,它在BeanFactory的基礎(chǔ)上增加了更容易與Spring AOP集成的功能,語言資源處理(用于國際化),發(fā)布事件,和程序應(yīng)用層的特定上下文(例如web應(yīng)用中的WebApplicationContext)。
總之,BeanFactory提供了配置框架和基本功能。ApplicationContext提供更多企業(yè)級應(yīng)用的相關(guān)功能。ApplicationContext是BeanFactory完整的超集,專門用于此章節(jié)。如果想使用BeanFactory來替代ApplicationContext,可以參考后面專門介紹BeanFactory的章節(jié)。
在Spring中,構(gòu)成你的應(yīng)用程序的主干對象(譯者注:例如發(fā)送郵件的對象),是被SpringIoC容器來管理的,它們被稱為bean。bean是一個對象,它被Spring IoC容器來實例化,組裝和管理。bean只是應(yīng)用程序中許多對象之一。bean和bean之間的依賴關(guān)系則反映在容器使用的配置中。
容器概述
org.springframework.context.ApplicationContext接口代表了Spring IoC容器,它負(fù)責(zé)實例化,組裝和配置上面所說的bean。容器通過讀取配置來實例化,組裝和配置bean。配置的主要形式有XML,Java注解和Java代碼,它允許你來定義應(yīng)用中的對象和他們之間復(fù)雜的依賴關(guān)系。
ApplicationContext接口的一些實現(xiàn)做到了開箱即用。在單機(jī)應(yīng)用中通常會創(chuàng)建一個ClassPathXmlApplicationContext和>FileSystemXmlApplicationContext的實例。
大多數(shù)應(yīng)用場景中,不需要用戶顯示的創(chuàng)建一個或多個Spring IoC容器的實例。例如,一個web應(yīng)用,簡單的8行(大約)代碼的web.xml文件即可滿足需求。(后面章節(jié)會給出在web應(yīng)用中如何方便的創(chuàng)建ApplicationContext實例)。如果你正在使用基于eclipse的Spring Tool Suite,那么這個配置將會很容易創(chuàng)建,只需要為數(shù)不多的鍵盤鼠標(biāo)操作。
下圖為Spring如何工作的抽象視圖。應(yīng)用中的類被配置文件整合起來了,所以在ApplicationContext被創(chuàng)建和初始化后,你已經(jīng)有一個完全配置和可執(zhí)行的系統(tǒng)或應(yīng)用了。

配置元數(shù)據(jù)
如上圖展示的,Spring IoC容器使用了配置;作為一個應(yīng)用開發(fā)者,這些配置可以讓你告訴Spring容器去實例化,配置,組裝應(yīng)用程序中的對象。
配置元數(shù)據(jù)傳統(tǒng)上是使用簡單,直觀的XML文件,這節(jié)中,主要用XML配置來說明Spring IoC容器的關(guān)鍵概念和特性。
注意:基于XML并不是配置的唯一途徑。Spring IoC容器與配置文件完全解耦。現(xiàn)在,許多開發(fā)者在他們的Spring應(yīng)用中選擇使用基于Java的配置(后續(xù)章節(jié)會介紹此部分內(nèi)容)。
關(guān)于配置Spring容器的其他形式信息,可以參考:
基于注解配置:Spring 2.5引入的基于注解配置的元數(shù)據(jù)。
基于Java配置:Spring 3.0開始,Spring JavaConfig提供的許多特性已經(jīng)成為了Spring Framework的核心。你可以使用Java而不是XML文件來定義程序類外部的bean。要使用這些新特性,可以參考@Configuration,@Bean,@Import和@DependsOn等注解。
Spring的配置由至少一個,通常多個Spring容器管理的bean定義組成?;赬ML來配置bean,是配置在<bean/>元素內(nèi),其父元素,也是頂級元素為<beans/>?;贘ava配置通常用@Bean來標(biāo)注方法或用@Configuration來標(biāo)注類。
這些bean定義對應(yīng)于構(gòu)成應(yīng)用程序的實際對象。通常你定義服務(wù)層對象,數(shù)據(jù)訪問層對象(DAO),展示層對象例如Struts Action實例,基礎(chǔ)底層對象例如Hibernate的SessionFactoriesJMS Queues等等。通常,不會在容器中配置細(xì)粒度的domain對象(例如數(shù)據(jù)庫po對象),因為加載這些類通常是DAO和業(yè)務(wù)邏輯代碼的職責(zé)。然而,你可以利用Spring集成的AspectJ來在Spring IoC容器之外創(chuàng)建和加載domain對象。這部分內(nèi)容在后面相關(guān)章節(jié)會給出介紹。
下面的例子基于XML配置的基本結(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="...">
<!-- 在這里定義bean和它相關(guān)的內(nèi)容 -->
</bean>
<bean id="..." class="...">
<!-- 在這里定義bean和它相關(guān)的內(nèi)容 -->
</bean>
<!-- 更多的bean定義... -->
</beans>
屬性id是一個字符串,是某個bean的唯一標(biāo)識。class屬性用來定義bean的類型,這里需要使用類的完全限定名。id屬性的值可以被協(xié)作的bean來引用。關(guān)于協(xié)作bean的配置,這個例子并沒有給出,但是可以參考下面Dependencies章節(jié)。
實例化一個容器對象
實例化一個Spring IoC容器非常簡單。傳遞給ApplicationContext構(gòu)造方法的相對路徑或絕對路徑,實際上是資源的路徑,它允許容器從外部各種資源(例如本地文件系統(tǒng),Java類路徑等等)加載配置元數(shù)據(jù)。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
學(xué)習(xí)了Spring IoC容器之后,你可能會想去知道更多有關(guān)于Spring的
Resource,像在Resource章節(jié)描述的一樣,Spring Resource提供了方便的機(jī)制來使用URI規(guī)則讀取InputStream。尤其是,Resource路徑被用來構(gòu)建應(yīng)用程序context,下面章節(jié)會給出具體描述。
下面的例子展示了service層對象(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ù)處理層對象的配置:
<?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>
上面的例子,service層由PetStoreServiceImpl和兩個JpaAccountDao、JpaItemDao類型的數(shù)據(jù)處理對象(基于JPA標(biāo)準(zhǔn))組成。property標(biāo)簽的name屬性的值意為Java Bean屬性的名稱,ref屬性值則為引用的另一個定義好的bean。id和ref的關(guān)聯(lián),表達(dá)了對象之間的關(guān)系和相互引用等。對象之間詳細(xì)的依賴關(guān)系介紹,可以參考后面的Dependencies章節(jié)。
編寫基于XML的配置文件
通過多個XML配置文件來定義bean是非常有必要的。通常,在你的架構(gòu)中,每一個XML配置文件表示特定的邏輯層或模塊。
你可以使用application context構(gòu)造方法來從所有的XML來加載bean。這個構(gòu)造方法可以接受多個資源,如上一節(jié)所示。還有一種選擇,使用<import/>標(biāo)簽來加載其他的XML配置文件,如下面例子:
<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定義來自三個XML文件,services.xml,messageSource.xml,和themeSource.xml。所有的路徑都是以下幾種,此文件的相對路徑,所以services.xml必須是同一目錄下,或者是類路徑,例如messageSource.xml和themeSource.xml。正如你看到的一個主要的斜線被忽略了,這因為路徑是相對的,所以最好不要使用斜線。被導(dǎo)入文件的內(nèi)容,包括最頂級標(biāo)簽<beans/>,必須是符合Spring標(biāo)準(zhǔn)的有效XML bean定義。
引用父目錄文件的時候,使用相對路徑“../”,這樣做是可以的,但是并不推薦這樣做。這樣做會創(chuàng)建一個相對當(dāng)前應(yīng)用程序之外的文件的依賴關(guān)系。尤其是,這樣引用不推薦用于"classpath:"路徑(例如"classpath:../services.xml"),在運行時解析過程中會選擇“最近的”類路徑的根目錄,然后再尋找它的父目錄。classPath配置的改變,可能會導(dǎo)致選擇不同的、錯誤的目錄。
你也可以使用絕對路徑來代替相對路徑:例如“file:C:/config/services.xml” 或 “classpath:/config/services.xml”。然而,你可能意識到,這樣做會將你的應(yīng)用程序的配置,耦合到一個特定的位置上。一般不會直接直接配置這種絕對路徑,例如,可以使用"${…}"占位符來獲得JVM運行時的系統(tǒng)屬性。
import標(biāo)簽是beans命名空間本身提供的特性。在Spring提供的XML名稱空間功能里,除了簡單的bean定義之外,還有更多的配置特性,例如“context”和“util”名稱空間等。
Groovy利用DSL來定義Bean
作為更進(jìn)一步展示配置元數(shù)據(jù),定義bean也可以用Spring的Groovybean定義DSL,從Grails框架也可以了解到這些知識。作為標(biāo)志,這些配置會存檔在后綴為“.groovy”的文件中,它的結(jié)構(gòu)如下所示:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
這個配置幾乎等同于用XML配置bean,甚至支持Spring的XML配置命名空間。它也可以通過importBeans指令來導(dǎo)入其他的XMLbean配置文件。
使用容器
接口ApplicationContext是一個高級工廠的接口,它能夠維護(hù)不同bean及其依賴項的注冊表。使用方法getBean(String name, Class<T> requiredType)可以讓你檢索到bean的實例。
接口ApplicationContext可以讓你讀取bean的定義并訪問他們,如下所示:
// 創(chuàng)建ApplicationContext實例,加載bean配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 從上述配置文件中配置的bean中,檢索你所需要的bean
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用配置好的實例,這就是具體的業(yè)務(wù)代碼了
List<String> userList = service.getUsernameList();
使用groovy配置的時候,看起來與上面非常類型,只不過換成ApplicationContext的其他實現(xiàn)類:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最靈活的方式是使用GenericApplicationContext,它整合了具有代表性的幾種reader,例如為XML準(zhǔn)備的XmlBeanDefinitionReader:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
或者為groovy準(zhǔn)備的GroovyBeanDefinitionReader:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
這樣reader就可以被整合,匹配到同一個ApplicationContext中來讀取多種類型的配置。
然后你可以通過getBean來檢索你的bean的實例了。ApplicationContext接口也有一些其他的方法來檢索bean,但是你的代碼中不太可能用到。實際上,你的代碼里連getBean()都不需要調(diào)用,所以也就不需要依賴于Spring的API。例如,Spring與web框架的集成,為各種web框架組件(例如controller,JSF)提供了依賴注入,允許您通過配置聲明對特定bean的依賴(例如:autowiring注解)。
bean概述
一個Spring IoC容器管理一個或多個bean。這些bean是根據(jù)你提供給容器的配置來被容器創(chuàng)建的,例如在XML配置中,<bean/>標(biāo)簽下的配置。
在容器內(nèi)部,這些bean被表示為BeanDefinition對象,它包含(以及其他信息)了如下的元數(shù)據(jù)信息:
一個限制包類名:通常是定義好的bean的實現(xiàn)類。
bean行為配置元素,規(guī)定了bean的容器中的行為方式(范圍,生命周期回調(diào)等等)。
對其他bean的引用;這些引用也被叫做協(xié)作者和依賴。
在新創(chuàng)建的對象中設(shè)置其他的配置項,例如,管理連接池的bean,你可以設(shè)置這個bean的可以使用的連接數(shù),或者最大,最小連接數(shù)。
這些元數(shù)據(jù)轉(zhuǎn)換為組成每個bean定義的一組屬性。
下面為具體定義bean的一些具體屬性:
| Property | 對應(yīng)的章節(jié)名 |
|---|---|
| class | 實例化bean |
| name | 命名bean |
| scope | bean應(yīng)用范圍 |
| 構(gòu)造方法傳參 | 依賴注入 |
| properties | 依賴注入 |
| 自動模式 | 自動裝配依賴 |
| 懶加載模式 | 懶加載bean |
| 初始化方法 | 初始化回調(diào) |
| 銷毀方法 | 銷毀后回調(diào) |
除了包含創(chuàng)建bean的特定信息以外,ApplicationContext的實現(xiàn)類還允許用戶在容器外創(chuàng)建現(xiàn)有對象。這是通過調(diào)用ApplicationContext的getBeanFactory()方法來實現(xiàn),這個方法會返回BeanFactory的實現(xiàn)類DefaultListableBeanFactory。它通過registerSingleton(..)和registerBeanDefinition(..)方法來支持這。但是,一般的應(yīng)用只通過配置來定義bean。
通過配置和手動創(chuàng)建單例時,需要今早的進(jìn)行實例化,這是為了讓容器在自動裝配和其他內(nèi)省步驟里正確的解析它們。雖然在某種程度上支持現(xiàn)有的元數(shù)據(jù)和現(xiàn)有的單例實例,但是在運行時(與工廠的實時訪問同時)注冊新bean并沒有得到官方的支持,并且可能導(dǎo)致在bean容器中并發(fā)訪問異常和/或不一致的狀態(tài)。
命名bean
每一個bean都有一個或多個標(biāo)識。這些bean的標(biāo)示符在容器內(nèi)部都必須是唯一的。bean一般只有一個標(biāo)示符,但是如果需要多個,其他的可以被認(rèn)為是別名。
基于XML配置中,你使用id或name屬性來指定bean的標(biāo)識。id屬性允許你指定一個唯一id。按照慣例它的命名是以英文數(shù)字組成的(“myBean”, “fooService”等),但是包含特殊字符也是可以的。如果你想給bean增加其他別名,那么可以使用name屬性,以“,”或“;”分割或空格。作為一個歷史記錄,在spring3.1之前的版本中,id屬性被定義為xsd: id類型,它可能限制了一些字符。截止到3.1,它被定義成xsd:string類型。要注意bean的id屬性值在容器中仍要要是唯一的,盡管不再使用XML解析器。
你不需要給bean設(shè)置一個id或name。如果沒有設(shè)置name和id,容器會給這個bean生成一個唯一的名稱。然而你想通過名稱來引用一個bean,你必須要提供一個名稱來使用ref標(biāo)簽或服務(wù)定位來查找。
bean命名約定
該約定是在命名bean時使用標(biāo)準(zhǔn)Java規(guī)范,例如字段名。主要是,bean的命名是以小寫字母開頭,之后使用駝峰是命名。舉例說明:"accountManager","accountService","userDao","loginController"等。
bean的命名應(yīng)該是讓你的配置更簡單易懂,在使用Spring AOP時采用這里的建議會對你有很大的幫助。
在classPath掃描組件時,Spring會給未命名的bean生成名稱,遵循上面說的規(guī)則:實質(zhì)上就是取簡單類型并將首字母小寫。然而在特殊情況下,類名的前兩個字符都是大寫的話,Spring會采用原始的命名,不會做改動,具體的邏輯可以參考Spring命名的方法
java.beans.Introspector.decapitalize。
bean的別名設(shè)置
在bean的定義本身,你可以給bean提供多個名稱,即利用id屬性提供一個唯一id或利用name屬性提供一個或多個名稱。這些名稱都是一個bean的等價名稱,這在某些情況下是比較有用的,例如應(yīng)用程序中的多個組件在引用同一個bean的時候,可以使用每個組件他們特定的bean名稱。
然而,在bean的定義處指定別名并不總是足夠的。有時候需要為在其他地方定義的bean引入別名。這樣的案例經(jīng)常出現(xiàn)在大規(guī)模系統(tǒng)中,每個子系統(tǒng)直接都可能有配置,每個子系統(tǒng)都有自己的一組對象定義?;赬ML的配置中,你可以使用<alias/>標(biāo)簽來實現(xiàn)這個。
<alias name="fromName" alias="toName"/>
這個例子中,在同意容器內(nèi)這個bean叫做“fromName”,也可以在用alias命名后被稱為“toName”。
例如,子系統(tǒng)A的配置文件可能去通過名稱subsystemA-dataSource來引用一個DataSource。子系統(tǒng)B的配置想通過名稱subsystemB-dataSource來引用一個DataSource``。當(dāng)子系統(tǒng)A、B組合到一起時,主系統(tǒng)會用myApp-dataSource```來命名。如果想讓同一對象擁有這三個名字,你可以在主系統(tǒng)中做如下配置
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
現(xiàn)在,每一個子系統(tǒng)和主系統(tǒng)都可以通過唯一的名稱來引用```DataSource``了,并且保證和其他的bean定義沒有沖突,其實它們引用的是同一個bean。
基于Java配置
如果你在使用基于Java的配置,可以用```@Bean```注解來設(shè)置別名,后面會有基于Java配置的章節(jié),會在那里詳細(xì)說明。
實例化bean
bean的配置,實質(zhì)上是為了創(chuàng)建一個或多個對象。容器在需要的時候,會查看bean是如何配置的,然后通過這個bean的配置去實例化一個對象。
如果你是基于XML來配置bean的,你可以在<bean/>的class屬性中指定對象的類型(或者說是class)。這個class屬性值,實際是BeanDefinition實現(xiàn)類的一個屬性,通常是必填項。有兩種使用Class屬性的方式:
作為代表性的,如果通過容器本身直接實例化bean,指定要構(gòu)建的bean的類,通過反射調(diào)用類的構(gòu)造方法,這等同于Java的
new操作。指定的實際類,含有一個靜態(tài)的工廠方法,調(diào)用這個工廠方法后可以創(chuàng)建對象實例,更少的時候,容器在類上調(diào)用靜態(tài)工廠方法去實例化bean。從調(diào)用靜態(tài)工廠方法返回的對象類型可以是相同的類或其他類。
內(nèi)部類名稱
如果你想為靜態(tài)內(nèi)部類配置bean定義,你需要使用內(nèi)部類的另一種名稱。
例如,如果你在com.example包下有一個Foo類,F(xiàn)oo有一個靜態(tài)內(nèi)部類叫做Bar,那么在定義bean的時候,class屬性的值就應(yīng)該寫作:
com.example.Foo$Bar
要注意要用"$"將類和內(nèi)部類隔開。
使用構(gòu)造方法實例化
當(dāng)你使用構(gòu)造方法來創(chuàng)建bean,所有的正常類都可以被Spring來兼容和使用。也就是說,這個類不需要去實現(xiàn)一個指定的接口,也不需要使用指定的某種方式去編寫。簡單的指明bean的class就足夠了。然后,取決于你使用哪種類的IoC容器來實現(xiàn)bean,你可能需要一個默認(rèn)的(空的)構(gòu)造方法。
Spring IoC容器幾乎可以管理任何你想管理的class。它并不局限于管理真正的javaBean。大部分使用Spring的使用者,更喜歡用默認(rèn)的(空的)構(gòu)造方法,或是class屬性來構(gòu)建bean。你也可以在容器中使用有意思的非bean風(fēng)格(non-bean-style)的class。例如,如果你需要使用一個完全不符合JavaBean規(guī)范的從前遺留下來的連接池,對此,Spring也一樣可以很好的管理。
基于XML的配置,你可以指定你的bean像下面這樣:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
關(guān)于如何向有參構(gòu)造方法傳遞參數(shù)(如果需要),以及在對象實例化后對象屬性如何設(shè)置,可以參開依賴注入章節(jié)。
使用靜態(tài)工廠方法實例化
當(dāng)你使用了靜態(tài)工廠方法來定義一個bean,你使用class屬性來指定的類需要包含靜態(tài)工廠方法,再利用factory-method屬性來指定這個靜態(tài)工廠方法的名稱。你可以調(diào)用此方法并返回一個對象實例。
下面的的bean定義中,bean會被調(diào)用靜態(tài)工廠方法來創(chuàng)建。這個定義中,并沒有指定返回的對象類型,只寫了含有此靜態(tài)工廠方法的類名。這個例子中,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;
}
}
關(guān)于給工程方法傳遞參數(shù),工廠方法返回對象后給對象實例屬性賦值的詳細(xì)說明,參考后面章節(jié):依賴和詳細(xì)配置。
使用工廠方法實例來實例化
類似于上節(jié)中的利用靜態(tài)工廠方法實例化,這里指的是利用一個已經(jīng)存在的bean,調(diào)用它的非靜態(tài)的工廠方法來創(chuàng)建bean。使用這種機(jī)制,class屬性是允許為空的,在factory-bean屬性中,指定當(dāng)前容器(或父容器)中包含的bean的名稱,這個bean包含調(diào)用之后可以創(chuàng)建對象的方法。然后在factory-method屬性中來指定這個方法的名稱:
<!-- 工廠bean, 包含了叫createInstance()的方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 注入這個bean需要的一些配置項 -->
</bean>
<!-- 這個bean通過上面的工廠bean來創(chuàng)建 -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
對應(yīng)的Java代碼:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
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"/>
相關(guān)Java代碼:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
這種方法表明,工廠bean本身可以通過依賴注入(DI)來管理和配置。參考后面章節(jié):依賴和詳細(xì)配置。
在Spring文檔中,工廠bean指的是在Spring容器中配置好的bean,它將通過一個實例工廠或靜態(tài)工廠來創(chuàng)建對象。與之相比,
FactoryBean(注意這個是一個類名)是指Spring中的一個接口,請不要混淆!
依賴
一個典型的企業(yè)級應(yīng)用并不是由相互獨立的對象組成(按照Spring的說法就是bean)。即使是最簡單的應(yīng)用程序,也是需要有一些對象相互協(xié)作才能將整和到一起的應(yīng)用呈現(xiàn)給終端用戶。
依賴注入(DI)
依賴注入是對象定義他們依賴的過程,這些依賴指的是與之一起協(xié)作的其他對象,只通過構(gòu)造方法參數(shù),工廠方法的參數(shù)或?qū)ο髮傩?調(diào)用構(gòu)造方法或工廠方法后得到的對象)。容器在創(chuàng)建bean之后注入它們的依賴。這個過程是從根本上反轉(zhuǎn)過來了,因此叫做控制反轉(zhuǎn)(IoC),bean自己控制實例化或定位它的依賴。
在使用DI機(jī)制時,代碼更簡潔,當(dāng)對象被提供給其依賴關(guān)系時,解耦更有效。對象并不去尋找它的依賴,也不知道依賴的位置和class。同樣的,你的class會更容易的去測試,特別是當(dāng)依賴是接口或抽象類時,可以用它們的子類或?qū)崿F(xiàn)類來實現(xiàn)單元測試。
DI主要存在兩種方式:基于構(gòu)造方法的依賴注入,基于setter的依賴注入。
基于構(gòu)造方法依賴注入
基于構(gòu)造方法的依賴注入是很成熟的,容器回去調(diào)用帶有一定數(shù)量參數(shù)的構(gòu)造方法,而其中的每一個參數(shù),則代表了一個依賴。調(diào)用一個帶有特定參數(shù)的靜態(tài)工廠方法與此是幾乎等效的,這里討論的是將參數(shù)用于構(gòu)造函數(shù),并以類似的方式處理靜態(tài)工廠方法。下面的例子展示了一個class只能通過用構(gòu)造方法來進(jìn)行依賴注入。要注意的是這個類沒有任何特別的地方,它只是一個POJO,不依賴與容器的特定接口,基類或注解等。
public class SimpleMovieLister {
// SimpleMovieLister有一個依賴是MovieFinder,簡單說就是有個MovieFinder類型的屬性
private MovieFinder movieFinder;
// 有了這個構(gòu)造方法,就可以將movieFinder傳進(jìn)來并賦值給屬性movieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 一些其他的業(yè)務(wù)邏輯,這里忽略. . .
}
處理構(gòu)造方法參數(shù)
處理構(gòu)造方法參數(shù)的時候,要注意匹配好參數(shù)的類型。bean定義中,如果構(gòu)造方法參數(shù)沒有潛在的歧義,那么在bean定義中,定義構(gòu)造函數(shù)參數(shù)的順序是在bean被實例化時,這些參數(shù)被提供給構(gòu)造函數(shù)的順序??紤]一下下面的類:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
不存在潛在的歧義,假設(shè)Bar和Baz沒有繼承關(guān)系。因此下面的配置是沒有問題的,你不必在<constructor-arg/>指定構(gòu)造方法參數(shù)的index或type屬性。
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
當(dāng)引用另一個bean時,就會知道該類型,并可以自動匹配(就像上面的示例一樣)。當(dāng)使用一個簡單類型,例如true,Spring并不能判定值的類型,所以不能完成自動匹配。參考下面的類:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
構(gòu)造方法參數(shù)類型匹配
前面的例子中,如果利用type指定了構(gòu)造方法參數(shù)的類型,那么容器是可以利用類型來自動匹配參數(shù)的。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
構(gòu)造方法參數(shù)下標(biāo)
使用index屬性去明確指定構(gòu)造方法參數(shù)的順序,例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解決多個值的歧義之外,指定索引還可以解決構(gòu)造函數(shù)具有相同類型的兩個參數(shù)的問題。要注意,index的值是從0開始的。
構(gòu)造方法參數(shù)名稱
你也可以利用構(gòu)造方法參數(shù)的名稱來消除歧義:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
記住,要使這正常使用,你的代碼必須使用debug級別來編譯,這樣Spring才可以查找到構(gòu)造方法的名稱(譯者注:關(guān)于利用debug來編譯,此處是一個重要的細(xì)節(jié),如果不了解,在今后的工作中,你可能會遇到一些莫名其妙的問題,詳情請點擊<font style=text-decoration:underline color=#548e2e>知識擴(kuò)展</font>)。如果你不能利用debug級別編譯程序(或者說你不想),你可以使用@ConstructorProperties注解去設(shè)置好你的構(gòu)造方法名稱。下面是一個簡單的例子:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于setter依賴注入
容器調(diào)用無參構(gòu)造方法或沒有參數(shù)的靜態(tài)工廠方法后,會得到bean的實例,基于setter的依賴注入則是在得到bean的實例后,容器調(diào)用bean實例的setter方法來注入依賴屬性。
下面的例子展示了只能通過setter方法依賴注入的類。這個類是非常常見的Java類。這是一個和容器特定接口沒用依賴的,沒用注解也沒有基類的POJO。
public class SimpleMovieLister {
// 有一個the MovieFinder的屬性,也就是依賴
private MovieFinder movieFinder;
// 有一個setter方法,所以Spring容器可以調(diào)用這歌方法來注入一個MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 其他邏輯則省略了...
}
你應(yīng)該記得前面內(nèi)容中介紹的ApplicationContext,它為它所管理的bean提供了基于構(gòu)造方法和setter的依賴注入。在通過構(gòu)造函數(shù)方法注入一些依賴項之后,它還支持基于setter方式的依賴注入。你以BeanDefinition的形式來配置依賴項,可以將其與PropertyEditor實例結(jié)合使用,以將屬性從一種格式轉(zhuǎn)換為另一種格式。然而許多Spring的使用者并不會直接用這些類,而是使用XML配置bean定義,帶有注解的組件(例如用@Component,@Controller注解標(biāo)注的類),或者帶有@Configuration注解的類中帶有@bean注解的方法。這些形式的配置最終都會被轉(zhuǎn)換到BeanDefinition實例的內(nèi)部,并被Spring IoC容器來加載實例。
選擇基于構(gòu)造方法還是setter的依賴注入?
你可以混合使用基于構(gòu)造方法的和setter的依賴注入,利用構(gòu)造方法的方式來強(qiáng)制依賴,利用setter的方式來做可選的依賴,這些方式是很不出錯的。注意,在setter方法上使用@Required注解來標(biāo)注,可以讓此屬性變?yōu)楸仨氉⑷氲摹?
Spring團(tuán)隊通常更贊成使用構(gòu)造方法的方式依賴注入。因為它支持將應(yīng)用程序組件以作為不可變對象來實現(xiàn),并確保所需的依賴項不是null。此外,依靠構(gòu)造方法注入的對象會在完全初始化后返回。另外,擁有大量的構(gòu)造方法是一個非常bad的代碼,這意味著這個類承載的太多的功能,需要重構(gòu)了。
基于Setter的依賴注入應(yīng)該主要用于可選的依賴項,可以用來給對象的一些屬性設(shè)置默認(rèn)值。否則,必須在代碼使用依賴項的地方進(jìn)行非空檢查。setter注入的一個好處就是setter方法可以讓對象在后面可以進(jìn)行二次配置或重新注入。```JMX```管理bean是利用setter注入的一個非常好的例子。
在處理一個第三方類并且這個類沒有源代碼時。這個第三方類沒有暴露任何setter方法,那么依賴注入的唯一途徑就是通過構(gòu)造方法。
依賴的解析過程
如下是容器解析bean依賴的過程:
ApplicationContext是通過配置元數(shù)據(jù)來創(chuàng)建和初始化的,這些元數(shù)據(jù)描述了所有的bean。配置元信息可以通過XML,Java代碼或注解來指定。對于每個bean,它的依賴用屬性,構(gòu)造方法參數(shù),或者靜態(tài)工廠方法參數(shù)的形式來表達(dá)。bean被創(chuàng)建好之后這些依賴會被提供給它。
每一個屬性或構(gòu)造方法參數(shù)都是要設(shè)置的值的實際定義,或者對容器內(nèi)另一個bean的引用。
每一個屬性或構(gòu)造方法參數(shù)所指定的值,都將被轉(zhuǎn)換為其實際類型的值。默認(rèn)情況下,Spring可以將以字符串格式提供的值轉(zhuǎn)換為所有內(nèi)置類型,如int,long,String,boolean等等。
容器在創(chuàng)建的時,Spring容器會驗證每一個bean的配置。然而,在實際創(chuàng)建bean之前,bean的屬性本身不會被設(shè)置。單例的和被設(shè)置為首先加載(pre-instantiated)的bean會在容器初始化后被創(chuàng)建。bean的范圍在下一章給出詳細(xì)介紹。除此之外,bean只會在需要它的時候被創(chuàng)建。創(chuàng)建bean的過程可能會引起一些列的bean被創(chuàng)建,例如bean的依賴、其依賴的依賴等等會被一起創(chuàng)建和分配。注意,在后面,依賴之間解析不匹配可能會顯現(xiàn)出來,即首先創(chuàng)建有影響的bean時候。
循環(huán)依賴
如果你主要使用構(gòu)造方法的方式注入,有可能造成無法解決的循環(huán)依賴。
例如,class A需要通過構(gòu)造方法注入一個Class B的實例,classB同樣需要通過構(gòu)造方法注入class A的實例。如果你為class A和class B配置bean并且互相注入,Spring IoC容器在運行時會發(fā)現(xiàn)這是循環(huán)引用,然后拋出異常:BeanCurrentlyInCreationException。
一個解決途徑就是去編輯編代碼,讓這些類可以通過setter注入。作為另一種選擇,避免使用構(gòu)造方法注入而只使用setter注入。換句話說,盡管這并推薦存在循環(huán)依賴,但是你可以使用setter來配置循環(huán)依賴。
與一般的情況(沒有循環(huán)依賴)不同,bean A和bean B之間的循環(huán)依賴關(guān)系迫使其中一個bean在被完全初始化之前注入另一個bean(典型的雞生蛋,蛋生雞場景)。
通常你可以信任Spring來做正確的事情。它可以發(fā)現(xiàn)配置問題,例如容器加載時發(fā)現(xiàn)引用不存在的bean,循環(huán)依賴等。bean在被實際創(chuàng)建后,Spring會盡可能晚的設(shè)置屬性和解決依賴。這意味著Spring容器正常加載后會晚一些拋出異常,也就是說只有當(dāng)你開始使用這個對象時,創(chuàng)建這個對象或他們的依賴時產(chǎn)生了異常。例如,因為缺失類屬性或無效的屬性值,bean會拋出一個異常。這可能會導(dǎo)致一些配置問題延遲出現(xiàn),這就是為什么ApplicationContext是默認(rèn)先將實例化單例的bean的。在bean被實際使用之前,需要提前花費一些實際和內(nèi)存,在創(chuàng)建ApplicationContext時發(fā)現(xiàn)配置問題,而不是以后來做這些事兒。你可以修改默認(rèn)的此行為,以便使單例的bean懶加載,而不會預(yù)先實例化。
如果沒有循環(huán)依賴的存在,當(dāng)一個或多個協(xié)作bean(其他bean的依賴)被注入到一個bean中時,每個協(xié)作bean在被注入到bean之前完全被配置。這意味著如果bean A依賴于bean B,Spring IoC容器在調(diào)用bean A 上的setter方法前將完全配置好bean B。換句話說,bean被實例化了(如果不是一個預(yù)先實例化的單例),他的依賴被設(shè)置了,相關(guān)的生命周期方法被調(diào)用了。
依賴注入的例子
下面的例子是用XML來配置的基于setter的依賴注入:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter注入,并且嵌套引用了另一個bean -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter注入,引用其他bean的方式看起來更加整潔 -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
相關(guān)Java代碼:
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;
}
}
上面例子中,聲明的setter與再XML中指定的類型相匹配。下面的例子使用了基于構(gòu)造方法的依賴注入(constructor-based):
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 構(gòu)造方法注入另一個bean -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- 構(gòu)造方法注入,這樣引用比上面更簡潔 -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
相關(guān)Java代碼:
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定義中指定的構(gòu)造方法參數(shù),會被ExampleBean構(gòu)造方法的參數(shù)來使用。
現(xiàn)在來考慮一下這個例子的變體,作為可以替代構(gòu)造方法的方式,Spring在之前告訴了你,可以使用靜態(tài)工廠方法來返回一個bean的實例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
相關(guān)Java代碼:
public class ExampleBean {
// 私有的構(gòu)造方法
private ExampleBean(...) {
...
}
// 一個靜態(tài)工廠方法; 此方法的參數(shù)可以認(rèn)為就是xml配置中引用的其他bean
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// 其他操作省略...
return eb;
}
}
靜態(tài)工廠方法的參數(shù)通過<constructor-arg />標(biāo)簽來提供,這點與基于構(gòu)造方法的注入恰好相同。工廠方法返回的class類型不一定要與其所在的類的類型相同,雖然在本例子中恰好是一樣的。實例工廠方法基本是以相同的方式來使用(除了使用factory-bean屬性來替代class屬性),這里不再給出詳細(xì)說明。
依賴和配置的細(xì)節(jié)
就像前面章節(jié)提到的,你可以定義bean屬性,構(gòu)造方法參數(shù)作為其他bean的引用,Spring的基于XML配置支持子標(biāo)簽<property/> 和 <constructor-arg/>等支持這個功能。
Straight values (基本類型, String等等)
標(biāo)簽<property/>的value屬性值,以可讀的字符串形式指定了屬性或構(gòu)造方法參數(shù)。Spring的轉(zhuǎn)換服務(wù)(即conversion service,后面章節(jié)會介紹)就是用來將這些屬性或參數(shù)的值從字符串轉(zhuǎn)換為它們的實際類型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
下面例子使用了更簡潔的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
前面的XML配置更加簡潔。然而,類似打字錯誤這樣的問題是在運行時被發(fā)現(xiàn)而不是在設(shè)計時,除非你使用的IDE是像<font style=text-decoration:underline color=#548e2e>IntelliJ IDEA</font>或<font style=text-decoration:underline color=#548e2e>Spring Tool Suite</font>(STS)這樣的,在你定義bean的時候可以自動填寫屬性值。這樣的IDE支持是強(qiáng)烈推薦的。
你也可以向下面這樣配置一個java.util.Properties實例:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring容器使用JavaBeans PropertyEditor的機(jī)制,將<value/>標(biāo)簽內(nèi)的值轉(zhuǎn)換到java.util.Properties實例中。這是一個非常好的簡寫方式,使用嵌套的<value/>標(biāo)簽而不是value屬性,也是Spring團(tuán)隊支持的幾個做法之一。
idref標(biāo)簽
標(biāo)簽idref是在容器中將另一個bean的id傳遞到<constructor-arg/>或<property/>標(biāo)簽中簡單的辦法,同時也有著簡單的錯誤檢驗功能。
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
上面的bean定義配置片段,正好等同于下面的配置片段:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一種形式要比第二種好,因為使用idref標(biāo)簽可以讓容器在部署時檢驗其引用的,以此命名的bean是否存在。第二個寫法中,沒有對傳遞給bean的targetName屬性的值執(zhí)行驗證。只有在bean被實例化的時候才會發(fā)現(xiàn)錯誤。如果這個bean是一個<font style=text-decoration:underline color=#548e2e>prototype bean</font>,這個錯誤可能會在容器部署成功后很長時間內(nèi)才被發(fā)現(xiàn)。
標(biāo)簽
idref上的local屬性,在4.0版本中的xsd就已經(jīng)不在支持了,因為它不能再為bean提供一個引用值了。當(dāng)升級至4.0版本時,只要將你項目中的idref local替換為idref bean即可。
內(nèi)部bean
將<bean/>定義在<property/>或<constructor-arg/>內(nèi),這樣定義的bean稱之為內(nèi)部bean(inner bean)。
<bean id="outer" class="...">
<!-- 不再引用一個bean,直接在這里定義一個 -->
<property name="target">
<bean class="com.example.Person"> <!-- 這就是內(nèi)部bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
內(nèi)部bean并不需要指定id和name屬性。如果指定了,容器并不會識別這兩個標(biāo)識。容器在創(chuàng)建bean實例的時候也會忽略scope屬性:內(nèi)部bean應(yīng)該是匿名的并且總是被伴隨著外部bean來創(chuàng)建的。不可能將內(nèi)部bean注入到除了它的外部bean以外的任何協(xié)作bean中,或者用其他的方式來訪問這個內(nèi)部bean。
作為一種不常見的情況,有可能從特定的域接受到銷毀的回調(diào)函數(shù),例如, request-scoped(請求域)的內(nèi)部bean包含在單例bean中,創(chuàng)建內(nèi)部bean實例的過程會綁定到其包含的bean中,但是銷毀的回調(diào)允許它參與request-scpoe的生命周期。這不是一個常見的場景。一般內(nèi)部bean也就是和包含他的bean共享作用域。
Collections
在<list/>,<set/>,<map/>和<props/>標(biāo)簽中,你可以設(shè)置屬性和參數(shù),分別是Java集合類型的List,Set,Map和Properties。
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- 實際調(diào)用setAdminEmails(java.util.Properties)方法 -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- 實際調(diào)用setSomeList(java.util.List)方法 -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- 實際調(diào)用setSomeMap(java.util.Map)方法 -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- 實際調(diào)用setSomeSet(java.util.Set)方法 -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map的key/value的值,或者set值,也可以是以下的這些元素:
bean | ref | idref | list | set | map | props | value | null
合并Collection
Spring容器也支持合并Collection。開發(fā)者可以在父bean中定義元素<list/>,<map/>,<set/> 或 <props/>,然后有子中也可以定義<list/>,<map/>,<set/> 或 <props/>,子類型的bean可以覆蓋和基礎(chǔ)父bean定義的集合元素。其實就是,子的值是合并了父子bean的元素的結(jié)果。子中的一個值,父中也有此值,那么會覆蓋父中的值。
本節(jié)只討論父子bean的機(jī)制。對于不熟悉父子bean的讀者,可以先去讀bean的繼承章節(jié)。
下面的例子師范了集合的合并(merge):
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- 注意,merge是在子的集合元素上來指定的 -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意,子bean的定義中,<props/>標(biāo)簽上設(shè)置了merge="true"。當(dāng)子bean被容器解析,實例化時,得到的實例有一個adminEmails屬性,這個屬性的值就是子bean屬性adminEmails和父bean屬性````adminEmails```合并后的結(jié)果。
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
這種合并同樣適用于<list/>,<map/>,<set/>等集合類型。使用<list/>場景下,有關(guān)List類型的語意,就是仍然保留了有序集合的概念。父中的值優(yōu)先于子中的值。使用Map,Set,Properties時并沒有順序的概念。因此,無序語義實際指的就是容器內(nèi)部使用的集合類型Map,Set和Properties等。
Collection merge的局限性
你不能合并不同的集合類型(例如Map和List),如果你這么做就會拋出異常。merge屬性必須用在低級別,繼承的,子的bean定義中;在父級別中設(shè)置````merge```是無效的,并不能得到你想要的結(jié)果。
強(qiáng)類型的Collection
Java 5中引入泛型之后,您可以使用強(qiáng)類型集合。就是,你可以聲明一個只允許包含String(只是舉一個例子)類型元素的集合。如果你使用Spring去向bean中依賴注入一個強(qiáng)類型集合,那么您可以利用Spring的類型轉(zhuǎn)換支持,這樣,強(qiáng)類型集合實例的元素就會在添加到集合之前轉(zhuǎn)換為適當(dāng)?shù)念愋汀?/p>
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
相關(guān)XML配置:
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
當(dāng)foo的accounts屬性準(zhǔn)備好被注入之后,元素的類型即強(qiáng)類型的Map<String, Float>就已經(jīng)能通過反射得到了。因此,Spring的類型轉(zhuǎn)換模塊,可以將各種值轉(zhuǎn)換為Float類型,即上例子中的字符串類型的9.99,2.75和3.99被轉(zhuǎn)換為Float類型的值。
Null和空字符值
Spring認(rèn)為屬性的的空參數(shù)為空字符串。下面的XML配置中,設(shè)置email屬性值為空字符串。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的配置等價于下面的Java代碼:
exampleBean.setEmail("");
標(biāo)簽<null/>可以處理null值,如下:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面的配置等同于下面的Java代碼:
exampleBean.setEmail(null);
使用p-namespace來簡寫xml配置
當(dāng)你定義你的屬性或其他協(xié)作bean時候,使用p-namespace(xml命名空間)可以讓你用<bean/>標(biāo)簽的屬性來替代<property/>標(biāo)簽。
在XML配置時,Spring支持利用名稱空間來擴(kuò)展配置。這節(jié)討論的bean配置格式僅僅是基于XML配置的。但是p名稱空間并不是在xsd中規(guī)定的,它值存在于Spring核心部分中。
下面的例子展示了兩個XML配置片段,他們是解決的是同一問題:第一個是使用標(biāo)準(zhǔn)XML配置格式,第二個使用的是p-namespace。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
這個例子展示了在bean定義中,p-namespace下有一個email屬性。這告訴了Spring這是一個聲明屬性的行為。像之前提到的,p-namespace并沒有在xsd中定義,所以你可以將其設(shè)置為屬性的名字。
下面的例子仍然包括兩個bean的定義,兩個都引用了另一個bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
如你所見,這里例子不光使用了p-namespace,還使用一個特殊格式來聲明一個屬性。第一個bean定義使用了<property name="spouse" ref="jane"/>來引用名稱叫jane的bean,第二個bean定義利用p:spouse-ref="jane"作為一個屬性,也引用了bean jane,在這里,spouse是屬性名稱,后半部分的-ref表示這里不是直接值,而是對另一個bean的引用。
p-namespace并不如標(biāo)準(zhǔn)XML定義靈活。例如聲明引用屬性時與以
Ref結(jié)尾的屬性沖突。我們建議你慎重選擇你的方法,并與團(tuán)隊成員做好溝通,避免同時使用全部三種方式。
使用c-namespace來簡寫xml配置
和上節(jié)的p-namespace類似,c-namespace是在Spring 3.1中引進(jìn)的,可以用它在行內(nèi)配置構(gòu)造方法的參數(shù),可以替代constructor-arg標(biāo)簽。
讓我們來回顧在章節(jié) 基于構(gòu)造方法依賴注入 中的例子,并用c-namespace來重寫一下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- 之前的方法 -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- 利用c-namespace 后 -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
使用c-namespace與p-namespace的用法類似,同樣的,它也需要被聲明,雖然并沒有在XSD中被規(guī)定(但是它在Spring core中可以被識別)。
在一些少見的情景下,例如你無法獲取到構(gòu)造方法參數(shù)名(如果字節(jié)碼文件是在沒有調(diào)試信息的情況下編譯的),這時可以使用參數(shù)位置下標(biāo):
<!-- c-namespace 下標(biāo)聲明 -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于XML的語法,下標(biāo)的符號需要引入"_"作為屬性的開頭,不能直接以數(shù)字開頭!(雖然一些IDE是允許的)
在實踐中,構(gòu)造函數(shù)解析機(jī)制在匹配參數(shù)方面是非常有效的,所以除非逼不得已才像上面這么做,我們建議在配置中使用名稱表示法來貫穿整個配置。
復(fù)合屬性名
當(dāng)設(shè)置bean的屬性時,你可以使用復(fù)合的,嵌套的屬性名稱,只要每一級的名稱都不為null??紤]下面的配置:
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
beanfoo有一個屬性叫fred,fred有一個叫bob的屬性,bob有一個sammy的屬性,然后最終的屬性sammy的值被設(shè)置為123。為了讓它起作用,bean的被構(gòu)建后,foo的屬性fred,fred的屬性bob不能為null,否則,就會拋出NullPointerException(空指針異常)。
使用 depends-on
如果一個bean是另一個bean的依賴,通常意味著一個bean被設(shè)置為另一個bean的屬性,你通常利用<ref/>屬性就可以搞定。然而,兩個bean之間的關(guān)系并不那么直接;例如,Java類中一個靜態(tài)的初始化方法需要被觸發(fā)。depends-on屬性可以明確的強(qiáng)制一個或多個bean在其屬性值所指定的bean初始化后再進(jìn)行初始化??梢詤⒖枷旅娴睦?,depends-on屬性來指定對一個bean有依賴:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
如果要依賴多個bean,只需要提供給depends-on屬性多個值即可,利用逗號,空格,分號來分割。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
bean定義中的
depends-on屬性,可以指定初始化時候的依賴,在單例bean中也可以指定銷毀時間的依賴。在此bean本身被銷毀之前,被指定的依賴bean首先被銷毀,因此,depends-on也可以控制關(guān)閉的順序。
懶加載bean
默認(rèn)的ApplicationContext初始化實現(xiàn)是馬上加載所有的bean。通常這么做是可取的,因為配置錯誤,環(huán)境錯誤會立即被發(fā)現(xiàn),而不是過了數(shù)小時或數(shù)天之后被發(fā)現(xiàn)。當(dāng)不需要這么做時,你可以通過配置bean定義為lazy-init(懶加載)來阻止bean的預(yù)實例化。配置好懶加載的bean,會告訴IoC容器,在需要使用這個bean實例的時候再加載這個bean,而不是容器初始化時立即加載這個bean。
在XML配置中,通過配置<bean/>標(biāo)簽中lazy-init屬性來實現(xiàn)懶加載;下面是例子:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
容器ApplicationContext在使用上面配置時,ApplicationContext不會再啟動時馬上加載叫lazy的bean,叫not.lazy的bean會被馬上加載。
然而,當(dāng)一個懶加載的bean是一個非懶加載的單例bean的依賴時,ApplicationContext會在啟動時立即實例化這個懶加載bean,這是因為容器必須要提供給這個單例bean的依賴。將懶加載的bean注入到其他非懶加載單例bean中。
你也可以利用<beans/>標(biāo)簽的default-lazy-init屬性,在容器級別就控制好懶加載:
<beans default-lazy-init="true">
<!-- 沒有bean會被提前實例化... -->
</beans>