1.1 spring IoC容器和beans的簡介
Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器凯亮,IoC也叫依賴注入(DI).不使用依賴注入時(shí)如果一個(gè)bean依賴其他bean處理過程是蔗彤,通過構(gòu)造函數(shù)或工廠方法的參數(shù)傳入,或者生成對象(構(gòu)造函數(shù)或?qū)ο蠊S)后通過set方法設(shè)置.生成一個(gè)bean時(shí),這個(gè)bean依賴的其他bean通過容器來注入,垒玲,這樣依賴的處理就不是由bean來處理唱歧,而是容器,這樣處理的過程就反轉(zhuǎn)了(inverse),因此叫控制反轉(zhuǎn)(IoC).
org.springframework.beans
和org.springframework.context
兩個(gè)包是實(shí)現(xiàn)spring IoC功能最基礎(chǔ)的兩個(gè)包.BeanFactory
接口提供一種能夠管理任何類型對象的高級配置機(jī)制.ApplicationContext
是BeanFactory
的子接口.ApplicationContext
增加與Spring AOP功能的集成;消息處理球及,事件發(fā)布;以及特定應(yīng)用層相關(guān)的Context比如用于web應(yīng)用中的WebApplicationContext
簡單來說氧骤,BeanFactory
接口提供了配置框架和基本功能,ApplicationContext
增加了更多針對企業(yè)應(yīng)用的功能.bean
的定義:在spring中吃引,構(gòu)成我們的應(yīng)用程序且被spring IoC容器管理的對象(object)就叫做bean
.bean是一個(gè)實(shí)例化筹陵,組裝并由Spring IoC容器管理的對象.bean只是應(yīng)用程序中眾多對象中的一個(gè)。 Bean和它們之間的依賴關(guān)系反映在容器使用的配置元數(shù)據(jù)中镊尺。
1.2容器概述
org.springframework.context.ApplicationContext
接口代表了Spring IoC 容器朦佩,負(fù)責(zé)實(shí)例化,配置庐氮,組裝前面所說的beans.IoC容器通過解析配置元數(shù)據(jù)獲取有關(guān)要實(shí)例化语稠,配置和組裝的對象的信息.配置元數(shù)據(jù)包 含在XML文件,java注解弄砍,或者java 代碼.這些元數(shù)據(jù)能夠表示組成我們應(yīng)用程序的對象以及這些對象之間豐富的依賴關(guān)系.
Spring提供了ApplicationContext
接口的幾個(gè)實(shí)現(xiàn).在獨(dú)立的應(yīng)用程序中通常會創(chuàng)建一個(gè)
ClassPathXmlApplicationContext
實(shí)例或FileSystemXmlApplicationContext
實(shí)例.然而XML文件是傳統(tǒng)配置元數(shù)據(jù)的方式仙畦,我們可以通過在XML文件中通過少許的配置输涕,使得容器支持java 注解,java代碼方式來配置元數(shù)據(jù).
在大多數(shù)應(yīng)用場景中议泵,用戶不需要顯式的實(shí)例化一個(gè)或者多個(gè)Spring IoC容器.比如在占贫,在一個(gè)web應(yīng)用中,在web.xml文件只需8行配置就可以了.
下圖從更抽象的角度來說明了Spring 是如何工作的.ApplicationContext
實(shí)例化后我們應(yīng)用程序里的classes與配置元數(shù)據(jù)就組合在一起先口,我們就得到一個(gè)配置完整且可執(zhí)行的系統(tǒng)或應(yīng)用程序.
1.2.1配置元數(shù)據(jù)
配置元數(shù)據(jù)代表了應(yīng)用開發(fā)者如何告訴spring 容器實(shí)例化型奥,配置,組裝應(yīng)用里的對象.配置元數(shù)據(jù)可以通過傳統(tǒng)的XML碉京,也可以通過基于java注解(spring2.5開始)厢汹,或基于java代碼(spring3.0開始).
bean對應(yīng)的實(shí)際對象組成了我們的應(yīng)用程序.我們通常會定義業(yè)務(wù)邏輯層,dao谐宙,持久化對象等烫葬,但是不會定義更細(xì)粒度領(lǐng)域?qū)ο笤谌萜髦校驗(yàn)槎x加載領(lǐng)域?qū)ο笫牵模粒虾蜆I(yè)務(wù)邏輯層的職責(zé).然而凡蜻,你可以使用Spring集成的 AspectJ去配置一個(gè)已經(jīng)生成的但不受Spring IoC容器控制的對象.
1.2.2容器初始化
初始化一個(gè)容器很簡單.只要給ApplicationContext
的構(gòu)造函數(shù)傳入元數(shù)據(jù)配置文件的路徑即可.元數(shù)據(jù)配置文件可以是系統(tǒng)本地文件搭综,也可以是java classpath的文件,還可以是其他的.
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
1.2.3使用容器
ApplicationContext
接口是一個(gè)能夠管理注冊在其上面的beans和這些beans依賴關(guān)系的高級factory.使用方法T getBean(String name, Class<T> requiredType)
就可以獲取你想要的bean的實(shí)例.ApplicationContext
允許你通過下面的方式去獲取bean和使用:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
最靈活的使用的方式是:GenericApplicationContext
結(jié)合reader 代理. 比如結(jié)合XmlBeanDefinitionReader
讀取xml:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
你可以使用getBean
去獲取bean實(shí)例.ApplicationContext
還提供了其獲取bean實(shí)例的方法划栓,理想情況下你的應(yīng)用程序就不該使用它們.事實(shí)上兑巾,你的應(yīng)用程序代碼根本不應(yīng)該調(diào)用該 getBean()
方法,因此完全不依賴于Spring API.
1.3 Bean概述
一個(gè)Spring IoC容器管理一個(gè)或者多個(gè)Beans.這些bean通過你提供給容器的配置元數(shù)據(jù)(configuration metadata )創(chuàng)建.比如忠荞,以XML <bean/>
定義的形式.
在容器本身中蒋歌,這些bean定義使用BeanDefinition
對象來表示,其中主要包含(還有其他信息)以下信息:
- 包限定類名稱:通常是所定義的bean的實(shí)際實(shí)現(xiàn)類
- Bean的行為狀態(tài) 委煤,說明它在容器的行為狀態(tài)(作用域堂油,生命周期回調(diào)等等)
- Bean的依賴關(guān)系
-
在新創(chuàng)建的對象中設(shè)置的其他配置設(shè)置,例如碧绞,用于管理連接池的Bean的連接數(shù)量或池的大小限制府框。
這些信息轉(zhuǎn)換成類屬性就就組成了bean定義.
下圖是bean定義
1.3.1命名Beans
每個(gè)bean有一個(gè)或多個(gè)標(biāo)識.這些標(biāo)識在容器中必須是唯一的.一個(gè)bean通常只有一個(gè)標(biāo)識,如果需要更多的標(biāo)識讥邻,那么其他的標(biāo)識就可以當(dāng)作別名.
在xml配置文件中寓免,可以使用id和name屬性標(biāo)識一個(gè)bean.id屬性只能設(shè)置一個(gè).如果想要給bean取其他別名,可以通過name屬性计维,名字之間可以通過逗號,分號撕予, 空格進(jìn)行分割.
name 和id不是必須提供的.如果沒有提供name或者id鲫惶,容器會生成一個(gè)唯一標(biāo)識給這個(gè)bean.如果想通過name來引用一個(gè)bean,你必須提供一個(gè)name.如果不提供name实抡,可以通過使用inner bean和autowiring collaborators
別名
略
1.3.2實(shí)例化bean
bean定義本質(zhì)上是創(chuàng)建一個(gè)或多個(gè)對象的配方.當(dāng)請求獲取bean時(shí)欠母,容器就會查找bean的配方欢策,然后用bean定義封裝的元數(shù)據(jù)去創(chuàng)建一個(gè)實(shí)際對象.
如果你使用基于XML的元數(shù)據(jù)配置,你需要在<bean/>
中指定實(shí)例化對象對應(yīng)的class
.class
屬性是必須的赏淌,它是BeanDefinition
實(shí)例的一個(gè)類屬性.class屬性可以通過以下兩種方式之一來使用:
- 容器通過反射的方式調(diào)用其構(gòu)造函數(shù)創(chuàng)建bean踩寇,這與java代碼使用
new
創(chuàng)建一個(gè)對象是一樣的. - 通過指定創(chuàng)建class對象的靜態(tài)工廠方法,容器調(diào)用這個(gè)類的靜態(tài)工廠方法來創(chuàng)建bean.通過靜態(tài)工廠返回的對象可能是同一個(gè)類也可能不是.
通過構(gòu)造函數(shù)實(shí)例化
當(dāng)通過構(gòu)造函數(shù)創(chuàng)建一個(gè)bean的時(shí)候六水,所有普通的類都可以被Spring使用和兼容.這樣正在開發(fā)的類不需要實(shí)現(xiàn)特定的接口或者使用特殊的方式編寫.只需要指定bean對應(yīng)的class即可.根據(jù)bean使用的IoC類型,需要提供一個(gè)默認(rèn)的無參構(gòu)造函數(shù).
Spring IoC容器可以管理任何你想讓容器管理的對象.不僅僅限于管理真正JavaBeans
.大多數(shù)Spring用戶俺孙,僅使用只有默認(rèn)構(gòu)造函數(shù)(無參)和setter,getter屬性方法的JavaBeans
.你可以把更多非JavaBeans
類型的類放到容器中去.
通過靜態(tài)工廠方法實(shí)例化
當(dāng)你定義一個(gè)使用靜態(tài)工廠方法來創(chuàng)建的bean時(shí)掷贾,你在<bean/>
的class
屬性指定這個(gè)帶靜態(tài)工廠方法的類睛榄,同時(shí)在factory-method
指定這個(gè)類的靜態(tài)工廠方法名字.您應(yīng)該能夠調(diào)用此方法(使用后面介紹的可選參數(shù))并返回一個(gè)活動對象,這個(gè)對象就像使用通過構(gòu)造函數(shù)創(chuàng)建的的一樣.這種bean實(shí)例化方法可以用于那些使用靜態(tài)工廠創(chuàng)建對象的老代碼中.
<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;
}
}
通過'實(shí)例工廠方法'實(shí)例化
與通過靜態(tài)工廠方法實(shí)例化相似想帅,通過'實(shí)例工廠方法'實(shí)例化調(diào)用一個(gè)已存在bean的非靜態(tài)方法創(chuàng)建一個(gè)新的bean.使用這種方式场靴,class
屬性需要留空,factory-bean
屬性設(shè)置當(dāng)前容器已存在的bean港准,factory-method
設(shè)置為factory-bean
中用來創(chuàng)建對象的方法.
<!-- 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();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一個(gè)工廠類可以包含多個(gè)工廠方法旨剥,如下所示:
<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();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
這種方法表明,factory bean本身可以通過依賴注入(DI)進(jìn)行管理和配置.在Spring文檔中浅缸,factory bean指的是在Spring容器中配置的bean轨帜,它將通過實(shí)例或 靜態(tài)工廠方法創(chuàng)建對象 。相反疗杉,FactoryBean
(注意大寫字母)是指特定于Spring的 FactoryBean
阵谚。
1.4依賴
一個(gè)典型的企業(yè)應(yīng)用不會只有單個(gè)對象(或者spring所說的bean).即使最簡單的應(yīng)用程序也會有幾個(gè)對象一起工作,給終端用戶的是一個(gè)完整應(yīng)用.下一節(jié)將介紹如何從定義許多獨(dú)立的bean定義到這些bean對象相互協(xié)作從而實(shí)現(xiàn)一個(gè)完整的應(yīng)用程序.
1.4.1依賴注入
依賴注入(DI)是對象獲取它依賴的過程.也就說烟具,其他和它一起工作的對象通過構(gòu)造函數(shù)參數(shù)梢什,工廠方法參數(shù),工廠方法實(shí)例化或構(gòu)造函數(shù)實(shí)例后設(shè)置屬性這些方式注入.容器創(chuàng)建bean的時(shí)候會注入它的依賴朝聋,這個(gè)過程跟前面對象獲取它依賴的方式相反嗡午,因此也叫控制反轉(zhuǎn)(IoC),bean它本身控制依賴的實(shí)例或定位是通過這些類的直接構(gòu)造或服務(wù)定位器模式.依賴注入(DI)與控制反轉(zhuǎn)(IoC)表達(dá)的都是對象獲取它依賴的方法.通過注入的方式獲取依賴叫做依賴注入,通過注入的方式獲取依賴與通常獲取依賴的方式相反冀痕,所以叫控制反轉(zhuǎn).
通過DI代碼更加清晰荔睹,并且在對象提供依賴關(guān)系時(shí)解耦更有效.該對象不查找其依賴項(xiàng),并且不知道依賴項(xiàng)的位置或類.這樣言蛇,你的類測試更加容易僻他,特別是當(dāng)依賴的是一個(gè)接口或抽象基類時(shí),它們允許在單元測試中使用存根或模擬實(shí)現(xiàn)
基于構(gòu)造函數(shù)的依賴注入
基于構(gòu)造函數(shù)的依賴注入的實(shí)現(xiàn)通過調(diào)用包含多個(gè)參數(shù)的構(gòu)造函數(shù)實(shí)現(xiàn)的腊尚,每個(gè)參數(shù)就是一個(gè)依賴.與通過調(diào)用包含特殊參數(shù)的靜態(tài)工廠方法創(chuàng)建bean是等價(jià)的.下面的例子展示了通過構(gòu)造函數(shù)實(shí)現(xiàn)依賴注入.注意到例子中的類沒有什么特別之處吨拗,它是一個(gè)不依賴容器任何接口,基類或者注解的POJO.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
構(gòu)造函數(shù)參數(shù)解析
構(gòu)造函數(shù)參數(shù)解析發(fā)生在使用這些參數(shù)的類型的時(shí)候.如果bean定義的構(gòu)造函數(shù)參數(shù)中沒有歧義,那么在bean定義中定義的構(gòu)造函數(shù)參數(shù)的順序就是在實(shí)例化bean時(shí)將這些參數(shù)提供給相應(yīng)構(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ù)參數(shù)的索引及類型.
<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時(shí),類型是知道的,解析時(shí)候可以匹配上(像前面的例子一樣).當(dāng)使用基本數(shù)據(jù)類型時(shí),比如true
擎析,Spring不能決定這個(gè)類型的值妥衣,因此根據(jù)類型就不能匹配上.思考下面的類:
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ù)參數(shù)類型匹配
在前面的場景中,如果使用type
屬性明確指定構(gòu)造函數(shù)參數(shù)類型,那么容器的參數(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ù)參數(shù)索引
可以使用index
屬性明確描述構(gòu)造函數(shù)參數(shù)的的索引.比如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
構(gòu)造函數(shù)參數(shù)名稱
你還可以使用構(gòu)造函數(shù)參數(shù)名稱去標(biāo)識value
以消除歧義.比如
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
基于Setter的依賴注入
基于Setter的注入實(shí)現(xiàn)方式是:容器通過調(diào)用無參的構(gòu)造函數(shù)或者靜態(tài)工廠方法實(shí)例化bean后,然后調(diào)用bean的setter方法把依賴注入進(jìn)來.
下面的例子展示了一個(gè)類用純setter方法實(shí)現(xiàn)依賴注入.這個(gè)類是傳統(tǒng)的java類仅乓,不依賴容器任何接口,基類或者注解的POJO.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext
管理的bean支持基于構(gòu)造函數(shù)的方式注入和基于setter方法的注入.它同樣支持基于構(gòu)造函數(shù)注入部分bean之后蓬戚,再使用setter依賴注入其他依賴.你可以配置依賴通過BeanDefinition
的形式.然而夸楣,大多數(shù)Spring的用戶都不會直接去使用這些類而是通過xml bean定義,組件注解(比如@Component
,@Controller
)子漩,使用@Configuration
注解的java類里的@Bean
方法.然后將這些源內(nèi)部轉(zhuǎn)換為實(shí)例BeanDefinition
并用于加載整個(gè)Spring IoC容器實(shí)例豫喧。
基于構(gòu)造函數(shù)注入還是setter方法注入?
你可以混合使用構(gòu)造函數(shù)注入和setter方法注入幢泼,一條比較好經(jīng)驗(yàn)法則是:必須的依賴通過構(gòu)造函數(shù)注入紧显,可選的依賴通過setter方法注入.請注意,可以使用 setter方法上的@Required注釋來使該屬性成為必需的依賴項(xiàng).
Spring團(tuán)隊(duì)主張使用構(gòu)造函數(shù)的方式注入缕棵,因?yàn)樗沟萌藗兛梢詫?yīng)用程序組件實(shí)現(xiàn)為不可變對象孵班,并確保所需的依賴關(guān)系不是null.此外通過構(gòu)造函數(shù)注入的組件,它返回給調(diào)用方的是一個(gè)已經(jīng)初始化好的組件.從另一個(gè)角度來說招驴,包含大量參數(shù)的構(gòu)造函數(shù)有一種爛代碼的味道篙程,說明了這個(gè)類承擔(dān)了太多的責(zé)任應(yīng)該拆分開來.
Setter注入主要只應(yīng)用于可選的依賴關(guān)系,這些依賴關(guān)系可以在類中分配合理的默認(rèn)值别厘。否則虱饿,必須在代碼使用依賴關(guān)系的任何地方執(zhí)行非空檢查。setter注入的一個(gè)好處是setter方法使得該類的對象可以重新配置或稍后重新注入.
依賴的處理過程
容器解決依賴的過程:
- 1
ApplicationContext
通過配置元數(shù)據(jù)去創(chuàng)建和初始化触趴,配置元數(shù)據(jù)描述了所有的bean.配置元數(shù)據(jù)可以是XML氮发,java 代碼,注解. - 對于每個(gè)bean冗懦,它的依賴關(guān)系以屬性爽冕,構(gòu)造函數(shù)參數(shù)或靜態(tài)工廠方法(如果不是用正常的構(gòu)造函數(shù))的參數(shù)的形式表示.當(dāng)實(shí)際創(chuàng)建bean的時(shí)候,它的依賴被提供.
- 每個(gè)屬性或者構(gòu)造函數(shù)參數(shù)被設(shè)置一個(gè)實(shí)際值或引用bean容器里其他bean
- 每個(gè)屬性的值或者構(gòu)造函數(shù)參數(shù)的值根據(jù)描述信息轉(zhuǎn)化成屬性或者構(gòu)造函數(shù)參數(shù)實(shí)際的類型.默認(rèn)情況下披蕉,Spring可以把一個(gè)字符串格式的值轉(zhuǎn)換成所有的內(nèi)置的類型扇售,如int,long,String,boolen等.
Spring容器在容器創(chuàng)建時(shí)驗(yàn)證每個(gè)bean的配置.但是前塔,在實(shí)際創(chuàng)建 bean之前,bean屬性本身不會被設(shè)置.當(dāng)容器創(chuàng)建的時(shí)候單例的bean被預(yù)先實(shí)例化(默認(rèn)情況下).作用域定義在 Bean 作用域.否則承冰,只有在請求時(shí)才創(chuàng)建bean.創(chuàng)建一個(gè)bean可能會導(dǎo)致一系列的bean被創(chuàng)建,因?yàn)閎ean的依賴關(guān)系及其依賴關(guān)系的依賴關(guān)系(等等)被創(chuàng)建和分配.注意到依賴不匹配的處理晚點(diǎn)會說明.
循環(huán)依賴
如果你主要使用構(gòu)造函數(shù)方式注入食零,你可能會碰到一個(gè)無法解析的循環(huán)依賴場景.
比如:類A需要類B的實(shí)例困乒,通過構(gòu)造函數(shù)注入.類B需要類A的實(shí)例,也是通過構(gòu)造函數(shù)注入.如果將類A和B的Bean配置為相互注入贰谣,則Spring IoC容器會在運(yùn)行時(shí)檢測到此循環(huán)引用娜搂,并引發(fā)一個(gè) BeanCurrentlyInCreationException
解決的辦法就是修改代碼,配置成setter方法注入而不是通過構(gòu)造函數(shù)注入.或者避免構(gòu)造函數(shù)注入而僅使用setter方法注入.換句話說吱抚,雖然不推薦.但是你可以通過setter方法注入配置一個(gè)循環(huán)依賴.
與典型情況(沒有循環(huán)依賴)不同百宇,bean A和bean B之間的循環(huán)依賴關(guān)系迫使其中一個(gè)bean在被完全初始化之前注入另一個(gè)bean(經(jīng)典的雞/雞蛋場景)
你可以相信Spring能夠正確的處理事情.它在容器加載的時(shí)候會檢測配置問題,比如引用不存在的bean,循環(huán)依賴.Spring創(chuàng)建bean的時(shí)候秘豹,盡可能遲的設(shè)置屬性值和解決依賴關(guān)系.這意味著携御,一個(gè)正確加載的spring容器在請求一個(gè)對象時(shí)也會產(chǎn)生異常,如果創(chuàng)建這個(gè)對象或它的依賴時(shí)出現(xiàn)問題.比如既绕,當(dāng)屬性值缺失或非法啄刹,bean會拋出異常.某些配置問題被發(fā)現(xiàn)會延遲是因?yàn)?code>ApplicationContext的默認(rèn)實(shí)現(xiàn)會預(yù)先實(shí)例化單例bean.在實(shí)際需要這些bean之前,為了創(chuàng)建這些bean需要一定的時(shí)間和內(nèi)存凄贩,您會在ApplicationContext
創(chuàng)建時(shí)發(fā)現(xiàn)配置問題誓军,而不是之后.你可以覆蓋默認(rèn)的實(shí)現(xiàn),這樣單例的bean會延遲實(shí)例化不是預(yù)先實(shí)例化
如果不存在循環(huán)依賴疲扎,一個(gè)或多個(gè)協(xié)作bean被注入到一個(gè)依賴bean之前都是完全配置.這意味著昵时,假如有個(gè)Bean A它依賴Bean B,容器調(diào)用Bean A的setter方法之前已經(jīng)完全配置好Bean B.換句話說,Bean被初始化椒丧,設(shè)置它的依賴壹甥,相關(guān)的生命周期方法會被調(diào)用.
依賴注入例子
下面是基于setter方法的依賴注入例子
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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;
}
}
前面例子是setters方法被聲明為與XML文件中指定的屬性相匹配.下面的例子是基于構(gòu)造函數(shù)的依賴注入:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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定義里描述的constructor-arg
會被當(dāng)作ExampleBean
構(gòu)造函數(shù)的參數(shù).
現(xiàn)在思考一下這個(gè)例子的一個(gè)變體,替換使用構(gòu)造函數(shù)瓜挽,使用靜態(tài)工廠方法返回一個(gè)實(shí)例對象.
<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"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
給靜態(tài)工廠方法提供參數(shù)通過<constructor-arg/>
元素盹廷,就像實(shí)際使用構(gòu)造函數(shù)一樣.靜態(tài)工廠方法返回的類型,不是必須與靜態(tài)工廠方法所在的類一樣久橙,盡管我們的例子是這樣子的.實(shí)例(非靜態(tài))工廠方法將以基本相同的方式使用(除了使用factory-bean屬性而不是class屬性之外)俄占,因此在此不討論細(xì)節(jié)
1.4.2依賴和配置詳解
如前一節(jié)所述,你可以定義bean屬性和構(gòu)造函數(shù)參數(shù)去引用其他beans(協(xié)作類)淆衷,或者內(nèi)聯(lián)的值.Spring基于XML的元數(shù)據(jù)配置提供了<property/>
和<constructor-arg/>
去實(shí)現(xiàn)這個(gè)目的.
直接值(數(shù)字缸榄,字符串等)
用來描述類屬性或構(gòu)造函數(shù)參數(shù)的<property/>
里的value
用字符串來表示.Spring的 conversion service會把字符串轉(zhuǎn)換成類屬性或者構(gòu)造函數(shù)參數(shù)的實(shí)際類型.
<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>
下面的例子使用p-namespace進(jìn)行更簡潔的配置
<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更加簡潔;然而類型檢查最好是運(yùn)行時(shí)而不是設(shè)計(jì)時(shí),除非你使用IDE祝拯,比如IntelliJ IDEA 或者Spring Tool Suite (STS)甚带,能夠在bean定義的時(shí)候自動屬性補(bǔ)全.推薦使用這樣的IDEA.
你可以配置一個(gè)java.util.Properties
實(shí)例她肯,如下:
<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 容器使用JavaBean的PropertyEditor
機(jī)制把value
里面的text轉(zhuǎn)換成java.util.Properties
實(shí)例.這是一個(gè)捷徑,它是Spring團(tuán)隊(duì)贊成<value/>
在value屬性樣式上使用嵌套元素的少數(shù)幾個(gè)地方之一
idref 元素
idref是一個(gè)簡單的防錯(cuò)方式鹰贵,將容器中另一個(gè)bean的id傳給<constructor-arg/>
or<property/>
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
上面的bean定義片段與下面的片段等價(jià)(在運(yùn)行時(shí))
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一種形式要好于第二種晴氨,因?yàn)槭褂?code>idref標(biāo)簽使得容器在部署的時(shí)校驗(yàn)它引用的bean是否存在.在第二種變體中,不會校驗(yàn)傳遞給client bean targetName屬性的值.只有在client bean實(shí)例化的時(shí)候才會發(fā)現(xiàn)錯(cuò)誤.如果client是一個(gè)原型bean碉输,這個(gè)錯(cuò)誤和異常只有在容器發(fā)布后才發(fā)現(xiàn).
其中一個(gè)共同的地方(至少在早期比Spring 2.0版本)<idref/>
元素的值在ProxyFactoryBean
bean定義的AOP攔截器中配置.指定攔截器名稱使用<idref/>
元素可以防止拼寫錯(cuò)誤一個(gè)攔截器的id.
引用其他beans(協(xié)作類)
略
內(nèi)部beans
一個(gè)<bean/>
元素在<property/>
or<property/>
里面因此叫內(nèi)部bean
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
內(nèi)部bean不需要定義一個(gè)id或者name;假如有籽前,容器也不使用這個(gè)值作為bean的標(biāo)識.容器創(chuàng)建內(nèi)部bean時(shí)同樣忽略它的作用域(scope)屬性:內(nèi)部beans總是匿名的且它永遠(yuǎn)是由外部的bean創(chuàng)建.不可能把一個(gè)內(nèi)部bean注入給一個(gè)協(xié)作bean或者獨(dú)立訪問他們.
集合
在<list/>,<set/>敷钾,<map/>枝哄,和<props/>元素中,你可以設(shè)置屬性或參數(shù)為java的集合里L(fēng)ist, Set, Map, and Properties
map的key或value,set的value,可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
spring容器同樣支持集合合并.開發(fā)者可以定義一個(gè)父級的<list/>, <map/>, <set/> or <props/>元素和定義子級的<list/>, <map/>, <set/> or <props/>元素去繼承和覆蓋父級集合的值.也就是說阻荒,子集合的值就是合并父結(jié)合的值和子集合的值.
這部分關(guān)于合并討論了父子bean機(jī)制挠锥。不熟悉父代和子代bean定義的讀者可能希望在繼續(xù)之前閱讀 相關(guān)章節(jié)
下面就是集合合并的例子
<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">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意在child bean的定義中,adminEmails屬性中的<props/>
元素使用了merge=true
當(dāng)child bean被容器解析并實(shí)例化時(shí)侨赡,生成的實(shí)例具有一個(gè)adminEmails Properties集合蓖租,該集合包含合并子集合 adminEmails與父adminEmails集合的結(jié)果.
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
childProperties的值繼承了parent所有property元素的值,并且child的support屬性值覆蓋了父類的support屬性值
這個(gè)合并行為同樣適用于 <list/>, <map/>, and <set/>集合.在<list/>元素中辆毡,與List相關(guān)的語義(即ordered 值集合的概念)被保留.父類值的順序全部排在子類值的前面.Map,Set,Properties沒有順序存在的.因此沒有順序語義關(guān)聯(lián)到這些集合.
集合合并的限制
你不能合并不同的集合類型(比如一個(gè)Map和一個(gè)List)菜秦,如果你試圖這樣做,就有Exception拋出.合并只能在更低的繼承的子類中.在parent類的集合中定義合并是多余的舶掖,達(dá)不到你想要的合并
強(qiáng)類型集合
隨著Java 5中引入泛型類型球昨,您可以使用強(qiáng)類型集合.比如,你就可以定義一個(gè)只包含String類型的集合.如果您使用Spring將強(qiáng)類型集合依賴注入到bean中眨攘,你可以充分利用Spring的類型轉(zhuǎn)換主慰,這樣強(qiáng)類型集合實(shí)例中的元素在添加到集合之前可以轉(zhuǎn)換成合適的類型.
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<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 bean 的accounts屬性準(zhǔn)備注入時(shí),有關(guān)強(qiáng)類型元素類型的泛型信息Map<String, Float>可通過反射獲得.這樣Spring的類型轉(zhuǎn)換機(jī)制就會識別各個(gè)元素值的類型為Float鲫售,字符串值的 9.99, 2.75, 3.99 就會轉(zhuǎn)換為實(shí)際的Float類型.
Null 和 空字符串值
Spring把properties的空值當(dāng)作空字符串.下面基于XML的元數(shù)據(jù)配置片段把email屬性值設(shè)置為空的字符串.
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
這個(gè)例子等價(jià)于java代碼
exampleBean.setEmail("");
<null/>
元素被處理成null值共螺,比如:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面的配置等價(jià)于java代碼:
exampleBean.setEmail(null);
XML使用p-namespace快捷方式
略
XML使用c-namespace快捷方式
略
符合屬性名稱
設(shè)置bean屬性的時(shí)候你可以使用復(fù)合的或者嵌套的屬性名稱,只要這個(gè)路徑除了最終屬性名稱外的組件不為空.思考下面的bean定義:
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
這個(gè)foo bean有個(gè)一個(gè)fred屬性情竹,fred屬性有一個(gè)bob屬性藐不,bob屬性有個(gè)sammy屬性,最終sammy屬性值被設(shè)置為123.foo bean創(chuàng)建完之后秦效,foo的fred屬性雏蛮,fred的bob屬性都不能為空,否則就會拋出NullPointerException.
1.4.3使用depends-on
如果一個(gè)bean是另一個(gè)bean的依賴阱州,那通常意味著這個(gè)bean被設(shè)置為另一個(gè)bean的屬性.通常挑秉,在基于XML的元數(shù)據(jù)配置中你可以使用<ref/>
元素實(shí)現(xiàn).然而,有時(shí)候beans之間依賴并不是那么直接;比如苔货,一個(gè)類中的一個(gè)靜態(tài)初始化器需要被觸發(fā)犀概,比如數(shù)據(jù)庫驅(qū)動注冊.depends-on
屬性明確要求一個(gè)或多個(gè)beans被初始化在使用這個(gè)元素的bean初始化之前.下面的示例使用depends-on
屬性來表示對單個(gè)bean的依賴關(guān)系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示對多個(gè)bean的依賴關(guān)系立哑,請?zhí)峁┮粋€(gè)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" />
1.4.4延遲初始化(lazy-init)bean
默認(rèn)情況下铛绰,ApplicationContext
的實(shí)現(xiàn)會把單例bean的創(chuàng)建和配置當(dāng)作它初始化過程的一部分.通常,預(yù)初始化是可取的木蹬,因?yàn)榕渲没颦h(huán)境的錯(cuò)誤會被立即被發(fā)現(xiàn)至耻,而不是幾小時(shí)或幾天后.當(dāng)這種行為是不可取的時(shí)候,你可以阻止單例bean的預(yù)初始化通過標(biāo)識這個(gè)bean定義是延遲初始化的.一個(gè)延遲初始化bean告訴IoC容器當(dāng)?shù)谝淮握埱筮@個(gè)bean實(shí)例的時(shí)候才創(chuàng)建這個(gè)bean镊叁,而不是啟動的時(shí)候.
在XML中,這個(gè)行為的控制通過<bean/>
元素的lazy-init
屬性.比如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
當(dāng)前面的配置被ApplicationContext
處理的時(shí)候走触,lazy bean不會被馬上預(yù)初始化在ApplicationContext
啟動的時(shí)候晦譬,然而not.lazy bean會被馬上預(yù)初始化.
然而,當(dāng)一個(gè)延遲初始化bean是一個(gè)非延遲初始化單例bean的依賴時(shí)互广,ApplicationContext
會創(chuàng)建延遲初始化bean的實(shí)例在啟動的時(shí)候敛腌,因?yàn)樗仨殱M足單例bean的依賴.延遲初始化bean被注入單例bean中而不是延遲初始化的.
你還可以控制容器級別的延遲初始化,通過使用<beans/>
的default-lazy-init
屬性.比如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5自動注入?yún)f(xié)作類
Spring容器可以自動裝配協(xié)作類之間的關(guān)系.你可以讓Spring去自動解決你的bean的協(xié)作類通過檢查ApplicationContext
的內(nèi)容.自動裝配有以下好處:
- 自動裝配可以顯著的減少所要描述的屬性or構(gòu)造函數(shù)參數(shù)
*自動裝配可以更新配置隨著對象的演變.
當(dāng)使用基于XML的元數(shù)據(jù)配置的時(shí)候惫皱,你可以通過<bean/>
的autowire
屬性設(shè)置成自動裝配模式.
自動裝配功能有四種模式.你可以設(shè)置每個(gè)bean的自動裝配模式以及自動裝配哪一個(gè).
Table 2. 自動裝配模式
模式 | 說明 |
---|---|
no | (默認(rèn))無自動裝配.Bean的引用必須通過定義一個(gè)ref元素像樊,大型的應(yīng)用開發(fā)不建議修改默認(rèn)配置,因?yàn)槊鞔_的指定協(xié)作者提供了更好的控制和清晰度.一定程度上它記錄了系統(tǒng)的結(jié)構(gòu) |
byName (按屬性名稱) | 自動裝配通過屬性名稱.Spring查找與需要注入的屬性名稱相同的bean .比如旅敷,如果一個(gè)bean定義設(shè)置成自動裝配通過名稱生棍,并且它有個(gè)master屬性(因此,它有一個(gè)setMaster()方法)媳谁,Spring查找一個(gè)名稱為master的bean且把它設(shè)置給這個(gè)屬性. |
byType (按屬性類型) | 允許屬性自動裝配如果容器中存在這個(gè)屬性類型的bean.如果這個(gè)類型的bean超過一個(gè)涂滴,就會拋出錯(cuò)誤異常,告訴你不可以使用byType 模式給這個(gè)bean自動裝配.如果沒有匹配的bean晴音,什么事也不會發(fā)生柔纵,這個(gè)屬性就沒有設(shè)置 |
constructor | 與 byType相似,不過這是提供給構(gòu)造函數(shù)參數(shù)的.如果容器中沒有一個(gè)與構(gòu)造函數(shù)參數(shù)類型一樣的bean锤躁,一個(gè)嚴(yán)重的錯(cuò)誤就會拋出. |
使用按屬性類型或者構(gòu)造函數(shù)自動裝配模式搁料,你可以使用數(shù)組和集合類型.在這種情況下,容器中的所有符合期望類型的自動裝配候選都被提供來滿足依賴關(guān)系.你可以自動裝配一個(gè)強(qiáng)類型的Maps如果期望的key類型是一個(gè)String.一個(gè)自動裝配Maps的值將會包含所有滿足預(yù)期類型的bean實(shí)例并且Maps keys與bean的名稱一致.
你可以將自動裝配行為與依賴檢查結(jié)合起來系羞,依賴檢查在自動裝配完成后執(zhí)行.
自動裝配的局限和缺點(diǎn)
自動裝配在一個(gè)項(xiàng)目中統(tǒng)一使用是最好的.如果通常不使用自動裝配郭计,開發(fā)人員可能會使用它來僅連接一個(gè)或兩個(gè)bean定義。
思考一下自動裝配的局限和缺點(diǎn):
- 在屬性和構(gòu)造函數(shù)參數(shù)中設(shè)置顯式依賴總是包含自動裝配的功能.你不能自動裝配所謂的簡單屬性比如數(shù)值觉啊,字符串拣宏,類.這個(gè)限制是通過設(shè)計(jì)的
- 自動裝配沒有顯式設(shè)置精確.盡管像前面表格提到的,Spring小心避免在可能會有意想不到的結(jié)果的情況下進(jìn)行猜測杠人,Spring管理的對象之間的關(guān)系不像文檔那樣精確
- Spring容器提供的布線信息工具可能不能用來自動生成文檔.
- 容器中的多個(gè)bean定義可能匹配setter方法或構(gòu)造函數(shù)參數(shù)中自動裝配的類型.對列表勋乾,集合宋下,Maps來說不是個(gè)問題.然而對于期望依賴一個(gè)單例bean的,這種二異性是無法解決的.如果沒有一個(gè)唯一的bean,就會拋出異常
接下來幾個(gè)場景辑莫,你有幾個(gè)選擇: - 放棄自動裝配学歧,使用顯式布線.
- 通過設(shè)置它的 autowire-candidate 屬性為false避免自動裝配一個(gè)bean,就像接下來的章節(jié)所講的
- 通過設(shè)置
<bean/>
元素的primary
屬性為true指定一個(gè)單例bean定義作為主候選 - 通過基于注解的配置實(shí)現(xiàn)更細(xì)粒度的控制各吨,像 基于注解的容器配置.所說的
bean不自動裝配
以每個(gè)bean為基礎(chǔ)枝笨,你可以讓一個(gè)bean不自動裝配.在Spring的XML格式中,設(shè)置<bean/>
的autowire-candidate
屬性為false就可以了.容器會使設(shè)置了這個(gè)屬性為false的bean不能使用自動裝配功能.(包括基于注解的配置比如@Autowired
.autowire-candidate
屬性只對基于類型的自動裝配有作用揭蜒,對于按名稱的自動裝配不起作用.因此横浑,如果名稱匹配,通過名稱自動裝配將注入一個(gè)bean屉更。
你同樣可以限制自動裝配候選類通過bean名稱的模式匹配.頂級<beans/>
元素接受一個(gè)或多個(gè)模式在它的default-autowire-candidates
屬性中.比如京髓,限制bean名稱后綴為Repository的候選類毡熏,通過提供一個(gè)值*Repository.如果定義多個(gè)模式衷佃,用空格或逗號或分號分割各個(gè)模式.顯式設(shè)置bean定義的autowire-candidate
屬性為true或false具有高優(yōu)先級侈离,這樣模式匹配就不起作用.
這些技術(shù)會很有用對于那些不想要通過自動裝配方式注入其他bean的beans來說.這并不意味著被排除的bean不能通過自動裝配進(jìn)來方式注入其他bean,而是說它自己不能被自動裝配進(jìn)其他bean
1.4.6方法注入
在大多數(shù)場景萨脑,在容器的大多數(shù)bean都是單例的.當(dāng)一個(gè)單例bean需要和另一個(gè)單例bean協(xié)作或一個(gè)非單例bean需要和另一個(gè)非單例bean協(xié)作的時(shí)候隐轩,你可以處理這個(gè)依賴通過定義一個(gè)bean作為另一個(gè)的屬性.一個(gè)問題出現(xiàn)當(dāng)bean的生命周期不一樣.假如一個(gè)單例bean需要使用一個(gè)非單例(原型)bean,也許在A的每個(gè)方法調(diào)用.容器只創(chuàng)建單例bean一次渤早,這樣只有一次機(jī)會設(shè)置這個(gè)值.容器不能給bean A提供一個(gè)新的bean B實(shí)例在bean A需要的時(shí)候.
一個(gè)解決的方法就是放棄一些控制反轉(zhuǎn).你可以讓bean A 察覺容器通過實(shí)現(xiàn)ApplicationContextAware
接口 并且通過調(diào)用getBean("B")向容器請求一個(gè)bean B實(shí)例(通常是新的)在bean A每次需要的時(shí)候.下面的例子說明了這種方法:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面這樣的處理职车,雖然功能實(shí)現(xiàn)了,但是這是不合適的蛛芥,因?yàn)闃I(yè)務(wù)代碼耦合了Spring框架.方法注入(Method Injection)是Spring IoC容器的高級功能提鸟,能夠低耦合的處理這樣的場景
Lookup方法注入
Lookup方法注入能夠讓容器覆蓋容器所管理的bean上的方法,以返回在容器中查找到的另一個(gè)名字的bean.lookup通常在調(diào)用一個(gè)原型bean上使用仅淑,就像前面章節(jié)描述的場景一樣.Spring框架實(shí)現(xiàn)方法注入通過字節(jié)碼生成庫CGLIB動態(tài)生成覆蓋父類方法的子類.
看之前CommandManager類的代碼片段称勋,你可以看到Spring容器會動態(tài)的覆蓋createCommand()方法的實(shí)現(xiàn).你的CommandManager類不會有任何的Spring依賴,正如在重寫的例子中可以看到的那樣:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在客戶端類包含要注入的方法涯竟,被注入的方法需要一個(gè)格式就像下面一樣:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的赡鲜,動態(tài)生成的子類就會實(shí)現(xiàn)這個(gè)方法.否則,動態(tài)生成的子類會覆蓋之前父類的方法.比如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
commandManager bean調(diào)用它的createCommand()方法當(dāng)它需要一個(gè)myCommand bean實(shí)例的時(shí)候.你必須小心的去開發(fā)myCommand bean把它設(shè)置成原型bean,這是我們實(shí)際需要的庐船,如果是單例bean银酬,每次都是返回同樣的myCommand bean實(shí)例.
等價(jià)的,在基于注解的組件模型中筐钟,你可以定義一個(gè)lookup方法通過@Lookup
注解:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者揩瞪,更常用的,你可以依靠lookup方法的返回類型找到目標(biāo)的bean
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
任意方法替換
略
1.5 Bean的作用域
作用域 | 描述 |
---|---|
單例(singleton) | (默認(rèn))bean實(shí)例的作用范圍是spring IoC容器創(chuàng)建后到spring IoC銷毀.每次請求bean實(shí)例都是獲取到同一個(gè)bean |
原型(prototype) | 每次請求bean的時(shí)候都返回一個(gè)新的bean實(shí)例.它的作用域是它所在的對象 |
請求(request) | bean實(shí)例的作用范圍這個(gè)請求的生命周期篓冲,請求完成后這個(gè)bean就被銷毀.因此每個(gè)請求都會創(chuàng)建自己的bean實(shí)例.只在web應(yīng)用中有效 |
會話(session) | bean實(shí)例的作用范圍是這個(gè)http session的生命周期.只在web應(yīng)用中有效 |
應(yīng)用程序(application) | bean實(shí)例的作用范圍是ServletContext 的生命周期.只在web應(yīng)用中有效 |
websocket(websocket) | bean實(shí)例的作用范圍是webscoket的生命周期 |
當(dāng)你創(chuàng)建一個(gè)bean定義的時(shí)候李破,你創(chuàng)建了一個(gè)創(chuàng)建實(shí)際類(在bean定義的class)實(shí)例的配方.一個(gè)bean定義是一個(gè)配方的概念很重要宠哄,因?yàn)檫@意味著,同樣一個(gè)類嗤攻,你可以創(chuàng)建很多實(shí)例對象用一個(gè)配方.
您不僅可以控制要插入到從特定的bean定義創(chuàng)建的對象中的各種依賴項(xiàng)和配置值毛嫉,還可以控制從特定的bean定義創(chuàng)建的對象的范圍.這種方法非常強(qiáng)大靈活,這樣你可以通過元數(shù)據(jù)配置指定創(chuàng)建對象的作用域代替java類級別的作用域.Bean可以定義多種作用域中的一種:開箱即用的妇菱,Spring框架支持6種作用域承粤,其中四種是在web應(yīng)用中才能使用.
下面幾種作用域是開箱即用的(ps:直接使用的意思).你可以創(chuàng)建自定義的作用域
表三 .Bean的作用域
作用域 | 描述 |
---|---|
單例(singleton) | (默認(rèn))bean實(shí)例的作用范圍是spring IoC容器創(chuàng)建后到spring IoC銷毀.每次請求bean實(shí)例都是獲取到同一個(gè)bean |
原型(prototype) | 每次請求bean的時(shí)候都返回一個(gè)新的bean實(shí)例.它的作用域是它所在的對象 |
請求(request) | bean實(shí)例的作用范圍這個(gè)請求的生命周期,請求完成后這個(gè)bean就被銷毀.因此每個(gè)請求都會創(chuàng)建自己的bean實(shí)例.只在web應(yīng)用中有效 |
會話(session) | bean實(shí)例的作用范圍是這個(gè)http session的生命周期.只在web應(yīng)用中有效 |
應(yīng)用程序(application) | bean實(shí)例的作用范圍是ServletContext 的生命周期.只在web應(yīng)用中有效 |
websocket(websocket) | bean實(shí)例的作用范圍是webscoket的生命周期 |
1.5.1單例作用域
單例bean只有一個(gè)共享實(shí)例被管理闯团,對同一個(gè)bean id的所有bean請求容器都返回同一個(gè)bean實(shí)例.
換句話說辛臊,當(dāng)你定義一個(gè)bean定義且設(shè)置它的作用域?yàn)閟ingleton,Spring IoC只會給這個(gè)bean定義創(chuàng)建一個(gè)實(shí)例.這些單例bean的單個(gè)實(shí)例被存儲在緩存中房交,后續(xù)所有請求和引用這個(gè)bean都返回緩存中的對象.
Spring 單例bean的概念與GoF設(shè)計(jì)模式所說的單例模式不同.GoF里的單例模式是通過硬編碼的方式使得每個(gè)ClassLoader
有且只能創(chuàng)建一個(gè)類實(shí)例.Spring里的單例范圍描述的是單個(gè)容器和單個(gè)bean.意思是說如果你在單個(gè)容器中給指定類定義了一個(gè)單例bean浪讳,然后Spring容器有且只給這個(gè)bean定義創(chuàng)建一個(gè)實(shí)例.單例作用域是Spring的默認(rèn)作用域.在XML定義一個(gè)單例,你可以這樣寫涌萤,比如:
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
1.5.2原型作用域
非單例,原型bean的部署結(jié)果是每次請求這個(gè)bean的時(shí)候都會創(chuàng)建一個(gè)新的bean實(shí)例.這樣口猜,bean注入其他bean或請求bean都只能調(diào)用容器的getBean()方法.通常负溪,原型bean用于所有有狀態(tài)的beans而單例bean用于無狀態(tài)的bean.
下圖說明了Spring的原型作用域.DAO通常不會定義成原型,因?yàn)橥ǔ5腄AO不保留任何會話狀態(tài)济炎,這使得開發(fā)者能夠更容易重用單例bean.
下面的例子在XML定義了一個(gè)原型bean:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
與其他作用域相比川抡,Spring不管理原型bean的完整生命周期:容器實(shí)例化,配置和以其他方式組裝原型對象须尚,并將其交給客戶端崖堤,而不再記錄該原型實(shí)例.這樣,盡管所有對象都會調(diào)用初始化生命周期回調(diào)函數(shù)而不管作用域耐床,但是在原型作用域中密幔,不會調(diào)用配置的銷毀生命周期回調(diào)函數(shù).客戶端代碼必須負(fù)責(zé)清除原型對象和釋放它所占有的寶貴資源.為了讓Spring容器釋放原型bean所占有的的資源,嘗試使用 bean post-processor,它擁有需要清除的bean的引用.
在某些方面撩轰,Spring容器對于原型bean的作用就像是java new操作符的替換.全生命周期管理過去都是又客戶端管理(Spring容器中bean的生命周期詳細(xì)信息胯甩,請看 Lifecycle callbacks.).
依賴原型bean的單例bean
當(dāng)你使用的單例bean依賴原型bean,需要知道的是依賴的解析在實(shí)例化的時(shí)候.這樣如果你依賴注入一個(gè)原型bean到一個(gè)單例bean,一個(gè)新的原型bean被實(shí)例化然后注入到單例bean中.這個(gè)原型實(shí)例是唯一的實(shí)例提供給單例bean.
然而堪嫂,假如你希望單例bean重復(fù)獲取新的原型bean在運(yùn)行時(shí)偎箫,你不能依賴注入一個(gè)原型bean給單例bean,因?yàn)橐蕾囎⑷胫l(fā)生一次皆串,當(dāng)容器實(shí)例化單例bean的時(shí)候會解析和注入它的依賴.如果你在運(yùn)行時(shí)不止一次需要一個(gè)新的原型bean實(shí)例淹办,請看方法注入
1.5.4請求(requset),會話(session),應(yīng)用上下文(application),WebSocket作用域
請求(requset)恶复,會話(session),應(yīng)用上下文(application),WebSocket作用域只在web應(yīng)用(即ApplicationContext
實(shí)現(xiàn)是基于web的怜森,比如XmlWebApplicaitonContext
)可用.如果你在通常的Spring IoC容器比如ClassPathXmlApplicaitonContext
使用這些作用域,一個(gè)IllegalStateException
就會拋出,說明這是一個(gè)未知的作用域
初始化一個(gè)web配置
為了支持bean的作用范圍在請求(requset)流纹,會話(session),應(yīng)用上下文(application),WebSocket,在定義你的bean之前需要一些小的初始化配置(這些初始化設(shè)置在標(biāo)準(zhǔn)的作用域單例或原型中不需要).
你如何完成初始化配置取決于你特定的Servlet環(huán)境.
如果你在Spring Web MVC訪問作用域bean吼拥,實(shí)際上請求是由Spring DispatcherServlet
來處理的,你不需要特別的設(shè)置:因?yàn)?code>DispatcherServlet已經(jīng)暴露了所有的相關(guān)狀態(tài).
如果你使用Servlet2.5 web容器灼捂,并且請求的處理不是Spring DispatcherServlet
(比如使用JSF或Struts),你需要注冊org.springframework.web.context.request.RequestContextListener
RequestContextListener ServletRequestListener
.對于 Servlet 3.0+煌恢,可以通過WebApplicationInitializer
接口以可編程的方式完成二汛,或者對于更老的容器婿崭,將以下聲明添加到Web應(yīng)用程序的web.xml文件中
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者拨拓,如果您的監(jiān)聽器設(shè)置存在問題,請考慮使用Spring 的 RequestContextFilter
氓栈。過濾器映射取決于周圍的Web應(yīng)用程序配置渣磷,因此您必須根據(jù)需要進(jìn)行更改:
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet
,RequestContextListener
,RequestContextFilter
三者所做的事情是完全一樣的,即綁定HTTP請求對象到處理請求的線程上.這使得請求和會話作用域的bean可以在調(diào)用鏈的更后面進(jìn)行訪問.
請求作用域
思考以下XML的bean定義
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器給每個(gè)http請求創(chuàng)建一個(gè)新的LoginAction bean 使用LoginAction bean定義.這樣,loginAction bean作用于http請求這個(gè)級別.你可以改變這個(gè)實(shí)例的內(nèi)部狀態(tài)只要你想授瘦,因?yàn)槠渌麖膌oginAction bean定義創(chuàng)建的bean實(shí)例看不到它的狀態(tài)改變.它們只屬于特定的請求.當(dāng)請求處理完成后醋界,作用于這個(gè)請求的bean就會被丟棄.
當(dāng)使用注解驅(qū)動的components或java config,@RequestScope
注解可以用來標(biāo)識一個(gè)compoment的作用域是請求.
@RequestScope
@Component
public class LoginAction {
// ...
}
會話作用域
思考以下XML的bean定義
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器使用UserPreferences 的bean定義創(chuàng)建一個(gè)UserPreferences bean給單個(gè)http會話生命周期.換句話說奥务,UserPreferences bean作用于http會話這個(gè)級別.與請求作用域bean一樣物独,你可以改變bean的內(nèi)部狀態(tài)只要你想,因?yàn)槠渌麖腢serPreferences bean定義創(chuàng)建的bean實(shí)例看不到它的狀態(tài)改變.因?yàn)樗粚儆谔囟ǖ臅挘?dāng)http會話銷毀氯葬,作用于會話的bean也會被銷毀
當(dāng)使用注解驅(qū)動的components 或Java config,@SessionScope
注解可以用來標(biāo)識一個(gè)compoment的作用域是會話
@SessionScope
@Component
public class UserPreferences {
// ...
}
應(yīng)用上下文作用域
思考以下XML的bean定義
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器在整個(gè)web應(yīng)用中使用AppPreferences 的bean定義創(chuàng)建一個(gè)AppPreferences bean一次.因此挡篓,appPreferences作用于ServletContext
級別,它被當(dāng)作ServletContext
的屬性來保存.這有點(diǎn)像單例bean帚称,但是與單例bean有兩個(gè)很重要區(qū)別:(1)它對每個(gè)ServletContext
來說是單例的官研,不是每個(gè)ApplicationContext
(在任何給定的Web應(yīng)用程序中可能有幾個(gè))(2)它實(shí)際上是公開的,因此是可見的作為ServletContext屬性
當(dāng)使用注解驅(qū)動的components 或Java config,@ApplicationScope
注解可以用來標(biāo)識一個(gè)compoment的作用域是應(yīng)用上下文.
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
作用域bean作為依賴
Spring IoC不僅管理你的bean的實(shí)例化闯睹,而且還管理協(xié)作類的連接(或者注入).如果你想注入(舉個(gè)例子)一個(gè)http請求作用域的bean到另一個(gè)更長生命周期的bean中戏羽,你可以選擇注入一個(gè)AOP代理類替代注入一個(gè)作用域bean.也就是說,你需要注入一個(gè)代理對象且暴露的pulic接口與作用域?qū)ο笠粯勇コ裕部梢詮南嚓P(guān)作用域(例如HTTP請求)中檢索真實(shí)目標(biāo)對象始花,并將方法調(diào)用委托給實(shí)際對象.
在下面的例子中只有一行配置,但是了解背后"為什么"與知道"怎么做"一樣重要
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
去創(chuàng)建這樣一個(gè)代理孩锡,你只需要在作用域bean定義(選擇創(chuàng)建代理類型和基于XML的配置)中插入一個(gè)子元素<aop:scoped-proxy/>
即可.為什么bean定義作用域于請求酷宵,會話和自定義作用域級別需要<aop:scoped-proxy/>
元素?讓我們看看下面的單例定義躬窜,對比我們之前的定義(注意下面的userPreferences bean定義是不完整的)
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中浇垦,單例bean userManager注入一個(gè)http session作用域的bean.這有個(gè)關(guān)鍵點(diǎn)是userManager是單例:它只被每個(gè)容器實(shí)例化一次,因此它的依賴只能注入一次.這意味著userManager將操作同一個(gè)userPreferences對象荣挨,也就是它第一次注入的那個(gè)對象.
這不是你想要的行為當(dāng)一個(gè)短生命周期的bean注入一個(gè)長生命周期的bean,比如注入一個(gè)http session作用域的協(xié)作類bean到一個(gè)單例bean中.相反男韧,你需要一個(gè)userManager對象,并且在http session生命周期默垄,你需要一個(gè)userPreferences對象來描述特定的http session.這樣容器創(chuàng)建一個(gè)對象(理想情況下是一個(gè) UserPreferences實(shí)例的對象),這對象所暴露的public 接口跟UserPreferences類完全一樣此虑,且可以獲取到真實(shí)的UserPreferences對象從作用域機(jī)制中.容器注入一個(gè)代理對象到userManager bean中,userManager bean并不知道這個(gè)UserPreferences引用是一個(gè)代理對象.在這個(gè)例子中口锭,UserManager實(shí)例調(diào)用依賴注入的UserPreferences對象的方法寡壮,它實(shí)際上調(diào)用的是代理對象的上的方法.這個(gè)代理對象然后從http session中獲取真正的UserPreferences對象,并將方法調(diào)用委托給查找到的真正的UserPreferences對象.
因此,你需要以下準(zhǔn)確和完整的配置當(dāng)需要注入請求和會話作用域bean到協(xié)作對象時(shí):
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
選擇創(chuàng)建的代理類型
默認(rèn)情況下况既,當(dāng)Spring容器給一個(gè)被<aop:scoped-proxy/>
元素標(biāo)識的bean創(chuàng)建代理的時(shí)候这溅,一個(gè)基于CGLIB的代理類被創(chuàng)建
CGLIB只接受public方法調(diào)用!不要在代理中調(diào)用非public方法,它們不會委托給實(shí)際作用域的目標(biāo)象.
或者棒仍,你可以配置Spring容器創(chuàng)建基于標(biāo)準(zhǔn)JDK的接口代理給這些作用域bean悲靴,通過設(shè)置<aop:scoped-proxy/>
元素的proxy-target-class
屬性為false.使用基于JDK的接口代理意味著你不需要在你的應(yīng)用程序的的classpath添加其他的庫生成代理.然而,這意味著這個(gè)作用域bean的class必須實(shí)現(xiàn)至少一個(gè)接口莫其,并且所有注入這個(gè)作用域bean的協(xié)作bean必須通過它們的一個(gè)接口來引用.
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
更多關(guān)于選擇基于類還是基于接口的代理的詳細(xì)信息癞尚,看代理機(jī)制
1.5.5自定義作用域
bean作用域機(jī)制是可擴(kuò)展的;你可以定義你自己的作用域,或者重新定義已存在的作用域乱陡,盡管這認(rèn)為是不好的浇揩,你不能覆蓋內(nèi)置的單例和原型作用域.
創(chuàng)建一個(gè)自定義作用域
為了集成自定義作用域到Spring容器,你需要實(shí)現(xiàn)org.springframework.beans.factory.config.Scope
接口憨颠,這個(gè)接口在這個(gè)章節(jié)會有說明.有關(guān)實(shí)現(xiàn)自定義作用域的方法胳徽,建議查看Spring框架本身提供的Scope
實(shí)現(xiàn)和Scope
的開發(fā)文檔javadocs,里面更詳細(xì)的說明了你需要實(shí)現(xiàn)的方法.
Scope
接口包含有四個(gè)方法:從作用域中獲取對象,從作用域中移除對象爽彤,允許它們銷毀.
下面的方法從底層作用域返回一個(gè)對象.例如养盗,session作用域?qū)崿F(xiàn),返回一個(gè)session作用域的bean(如果不存在适篙,方法就返回一個(gè)bean的新實(shí)例往核,然后把它綁定到這個(gè)session給將來引用.)
Object get(String name, ObjectFactory objectFactory)
下面的方法從底層作用域刪除一個(gè)對象.以實(shí)現(xiàn)session作用域?yàn)槔瑒h除一個(gè)session作用域的bean從底層的session中.被刪除的對象應(yīng)該返回嚷节,但是你可以返回null如果指定name的對象不存在.
Object remove(String name)
下面的方法注冊了一個(gè)回調(diào)聂儒,當(dāng)作用域的bean被銷毀或者指定作用域?qū)ο蟊讳N毀時(shí)需要回調(diào).有關(guān)銷毀回調(diào)的更多信息,請參閱javadocs或Spring Scope實(shí)現(xiàn).
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法返回底層作用域的一致性標(biāo)識符.每個(gè)作用域的標(biāo)識符是不一樣的.對于session作用域來說硫痰,它的標(biāo)識符可以是它的session標(biāo)識符.
String getConversationId()
使用自定義作用域
當(dāng)你寫和測試自定義作用域?qū)崿F(xiàn)的時(shí)候薄货,你需要讓Spring容器知道你的新作用域.下面的方法是注冊一個(gè)新的作用域到Spring容器中的核心方法
void registerScope(String scopeName, Scope scope);
這個(gè)方法定義在ConfigurableBeanFactory
接口,在大多數(shù)的ApplicationContext
實(shí)現(xiàn)中都可以它的BeanFactory
屬性來訪問這個(gè)接口.
registerScope(..)方法中的第一個(gè)參數(shù)是與作用域關(guān)聯(lián)的唯一名字;Spring容器本身的這些名字的例子是singleton和 prototype.registerScope(..)方法中的第二個(gè)參數(shù)實(shí)際是你想要注冊使用的自定義作用域?qū)崿F(xiàn)的實(shí)例.
假設(shè)你寫了自定義作用域?qū)崿F(xiàn)碍论,然后像下面一樣注冊.
下面的SimpleThreadScope是Spring包含的,但是默認(rèn)沒有注冊.跟你自定義實(shí)現(xiàn)作用域是一樣的.
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后創(chuàng)建符合作用域規(guī)則的自定義作用域的bean定義.
<bean id="..." class="..." scope="thread">
自定義作用域?qū)崿F(xiàn)中柄慰,不限制你只用編程的方式注冊你的作用域.你還可以注冊你的作用域鳍悠,使用CustomScopeConfigurer
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="bar" class="x.y.Bar" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="foo" class="x.y.Foo">
<property name="bar" ref="bar"/>
</bean>
</beans>
1.6定制一個(gè)bean的本質(zhì)
1.6.1生命周期回調(diào)
要與容器的bean生命周期管理交互,你可以實(shí)現(xiàn)Spring的InitializingBean
和DisposableBean
接口.容器調(diào)用InitializingBean
的afterPropertiesSet()
方法在bean完成初始化后坐搔,調(diào)用DisposableBean
的destroy()
在bean銷毀前.
JSR-250的@PostConstruct和@PreDestroy注解被認(rèn)為是Spring應(yīng)用程序接收生命周期回調(diào)的最佳實(shí)踐.使用這個(gè)注解意味著你的bean不用耦合Spring特殊的接口.詳情請看@PostConstruct and @PreDestroy.
如果不想用JSR-250注解藏研,但是仍然想要移除耦合,可以考慮在bean定義元數(shù)據(jù)中添加init-method和destroy-method.
在內(nèi)部概行,Spring框架使用BeanPostProcessor
實(shí)現(xiàn)來處理找到的任何回調(diào)接口并且調(diào)用適當(dāng)?shù)姆椒ǎ绻阈枰远x功能或者其它Spring不提供的生命周期行為蠢挡,你可以自己實(shí)現(xiàn)BeanPostProcessor
.更多信息,請看Container Extension Points
除了初始化和銷毀??回調(diào)之外,Spring管理對象還可以實(shí)現(xiàn)Lifecycle
接口业踏,以便這些對象可以參與由容器自身生命周期驅(qū)動的啟動和關(guān)閉過程
本節(jié)講解生命周期回調(diào)接口.
初始化回調(diào)
org.springframework.beans.factory.InitializingBean
接口允許執(zhí)行一些初始化工作在bean所有必須的屬性都被容器設(shè)置后.InitializingBean
接口只定義了一個(gè)方法:
void afterPropertiesSet() throws Exception;
不建議你使用InitializingBean
接口禽炬,因?yàn)樗拇a耦合了Spring.或者,你使用@PostConstruc
注解或者增加一個(gè)POJO初始化方法.在基于XML的元數(shù)據(jù)配置文件中勤家,你使用init-method
屬性去設(shè)置無參函數(shù).在基于Java代碼的配置中腹尖,你可以使用@Bean
的initMethod
屬性,查看 Receiving lifecycle callbacks.例如伐脖,下面的例子:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
它等價(jià)于
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但是沒有耦合Spring代碼.
毀銷回調(diào)
實(shí)現(xiàn)org.springframework.beans.factory.DisposableBean
允許一個(gè)bean在包含它的容器被銷毀時(shí)獲得回調(diào).DisposableBean
接口只包含一個(gè)單一的方法:
void destroy() throws Exception;
不建議你使用DisposableBean
接口热幔,因?yàn)樗拇a耦合了Spring.或者,你使用@PreDestroy
注解或者增加一個(gè)POJO初始化方法.在基于XML的元數(shù)據(jù)配置文件中讼庇,你使用destroy-method
屬性去設(shè)置一個(gè)無參函數(shù).在基于Java代碼的配置中绎巨,你可以使用@Bean
的destroyMethod
屬性,查看 Receiving lifecycle callbacks.例如蠕啄,下面的例子:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
它等價(jià)于
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是代碼沒有耦合Spring.
默認(rèn)初始化和銷毀方法
當(dāng)你寫一個(gè)初始化和銷毀方法场勤,但是不使用Spring提供的InitializingBean
和DisposableBean
接口方法時(shí),你通辰樾冢可以寫這些方法的使用名字為init()却嗡,initialize(),dispose()等等.理想情況下嘹承,這些名字的生命周期回調(diào)方法是標(biāo)準(zhǔn)的在整個(gè)項(xiàng)目窗价,這樣所有的開發(fā)者使用同樣的方法名和保證一致性.
你可以配置Spring去查找所有命名為初始化和銷毀的回調(diào)方法在每個(gè)bean中.這意味著,作為一個(gè)應(yīng)用開發(fā)者叹卷,可以在你的應(yīng)用程序類中使用一個(gè)叫做 init()的初始化回調(diào)撼港,而不需要在每個(gè)bean定義中配置init-method="init" 屬性.Spring IoC容器調(diào)用這個(gè)方法當(dāng)bean創(chuàng)建的時(shí)候.這個(gè)功能強(qiáng)制要求初始化和銷毀回調(diào)方法命名要有一致性.
假如你的初始化回調(diào)方法命名為init() 和銷毀回調(diào)方法命名為destroy(),你將會構(gòu)建類向下面的例子一樣:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
頂級的<beans/>
元素的default-init-method
屬性會使Spring IoC容器識別bean里的init方法作為初始化回調(diào)方法.當(dāng)bean創(chuàng)建和組裝骤竹,如果bean包含這個(gè)方法,它就會在適當(dāng)?shù)臅r(shí)間被調(diào)用.
配置銷毀回調(diào)方法也類似宽气,使用<beans/>
元素的default-destroy-method
方法.
當(dāng)存在bean的已經(jīng)有方法回調(diào),但是名字與一致性要求的不同踏揣,你可以覆蓋它通過<bean/>
的init-method 和 destroy-method 屬性.
Spring容器保證bean的所有依賴都提供后立即調(diào)用初始化回調(diào)方法.這樣初始化回調(diào)方法是原始bean引用調(diào)用的眯杏,這意味著AOP攔截器還未應(yīng)用于bean.一個(gè)目標(biāo)bean首先被完全創(chuàng)建,然后一個(gè)AOP代理(例如)的攔截器鏈應(yīng)用于它.如果你的目標(biāo)bean和代理是分開定義的懒震,你的代碼繞過代理直接與原始目標(biāo)bean交互.因此罩息,攔截器使用在初始化方法是不合適的,因?yàn)槟繕?biāo)bean的生命周期和代理/攔截器耦合在一起并且當(dāng)你的代碼直接與原始bean交互的時(shí)候會留下奇怪的語義.
混合生命周期機(jī)制
從Spring2.5開始个扰,你有三種方式控制bean的生命周期管理: InitializingBean
and DisposableBean
接口回調(diào);自定義init() 和 destroy()方法;和@PostConstruct
和 @PreDestroy
.你可以混合使用這樣注解
當(dāng)多個(gè)生命周期機(jī)制配置在一個(gè)bean瓷炮,并且每個(gè)機(jī)制配置配置不同方法名,這樣配置方法按照一下順序執(zhí)行.然而當(dāng)不同的生命周期機(jī)制配置相同的方法名递宅,方法只會執(zhí)行一次娘香,就像前面章節(jié)所述.
在一個(gè)bean上多個(gè)生命周期配置不同初始化方法苍狰,調(diào)用順序如下:
- 被@PostConstruct注解的方法
* InitializingBean
接口定義的 afterPropertiesSet()
- 自定義init()方法
銷毀方法調(diào)用的順序也一樣: - 被@PreDestroy注解的方法
* DisposableBean
接口定義的 destroy()
- 自定義destroy()方法
啟動和關(guān)閉回調(diào)
Lifecycle
接口給任何有自己生命周期需求的對象定義了基本的方法(比如開啟和停止某些后臺進(jìn)程)
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
所有Spring管理的對象都可以實(shí)現(xiàn)這個(gè)接口.這樣,當(dāng)ApplicationContext
本身接收到啟動和停止信號時(shí)烘绽,比如運(yùn)行時(shí)的停止/重啟場景淋昭,它將會級聯(lián)調(diào)用上下文中所有實(shí)現(xiàn)Lifecycle
接口的bean的對應(yīng)方法.它實(shí)現(xiàn)是通過委托LifecycleProcessor
:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意到LifecycleProcessor
本省繼承了Lifecycle
接口.它還添加了兩種其他方法來處理正在刷新和關(guān)閉的上下文.
注意常規(guī)的
org.springframework.context.Lifecycle
接口只是顯式開始/停止通知的簡單約定且意味著context刷新時(shí)間不會自動啟動.可以考慮實(shí)現(xiàn)org.springframework.context.SmartLifecycle
提供更細(xì)粒度在自動啟動的時(shí)候.同樣,請注意停止通知不保證在銷毀的之前到達(dá):在常規(guī)的關(guān)閉中诀姚,所有Lifecycle
beans首先會收到停止同知在銷毀回調(diào)被廣播之前.然而响牛,在上下文的生命周期中的熱刷新或中止刷新嘗試時(shí),只會調(diào)用銷毀方法赫段。
啟動和關(guān)閉的方法調(diào)用順序很重要.如果一個(gè)"依賴關(guān)系"存在任何兩個(gè)對象呀打,依賴方將會在依賴之后開始,停止在依賴之前.然而糯笙,有時(shí)候直接依賴是不知道的.你只知道某些類型的對象應(yīng)該開始在其他類型對象之前.在這些情況下贬丛,SmartLifecycle
接口定義了其他選項(xiàng),命名了一個(gè)getPhase()方法在超類接口
Phased
中
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
當(dāng)啟動的時(shí)候给涕,最低階段值的對象先啟動豺憔,當(dāng)停止的時(shí)候,順序相反.因此一個(gè)對象實(shí)現(xiàn)了SmartLifecycle
且它的getPhase()方法返回Integer.MIN_VALUE够庙,它就是第一個(gè)啟動和最后一個(gè)停止.在階段的另外一端恭应,如果一個(gè)階段值是Integer.MAX_VALUE意味著這個(gè)對象是最后一個(gè)啟動和最先停止的.當(dāng)考慮階段值的時(shí)候,知道沒有實(shí)現(xiàn)SmartLifecycle
的Lifecycle
對象的默認(rèn)階段值是0很重要.因此耘眨,任何負(fù)數(shù)的階段值意味著這個(gè)對象會比其他標(biāo)準(zhǔn)組件先啟動,反之相反.
正如你所見SmartLifecycle
的stop方法接受一個(gè)回調(diào).任何實(shí)現(xiàn)類必須調(diào)用callback的run()方法當(dāng)實(shí)現(xiàn)類的的關(guān)閉進(jìn)程完成后.這允許必要的時(shí)候啟用異步調(diào)用昼榛,因?yàn)?code>LifecycleProcessor接口的默認(rèn)實(shí)現(xiàn)DefaultLifecycleProcessor
將等待其每個(gè)階段中的對象組的超時(shí)值來調(diào)用該回調(diào).默認(rèn)每個(gè)階段的超時(shí)值是30妙.你可以覆蓋默認(rèn)的生命周期處理實(shí)例通過在上下文中調(diào)用一個(gè)名叫"lifecycleProcessor"的bean.如果你想要修改超時(shí)時(shí)間,下面的定義就夠了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述剔难,LifecycleProcessor接口定義了用于刷新和關(guān)閉上下文的回調(diào)方法.后者將簡單的驅(qū)動關(guān)閉進(jìn)程就好像stop()方法被調(diào)用一樣胆屿,但是當(dāng)上下文關(guān)閉的時(shí)候會發(fā)生.'刷新'回調(diào)啟用了SmartLifecycle
bean的另外一個(gè)功能.當(dāng)上下文刷新的時(shí)候(所有對象都實(shí)例化和初始化之后),回調(diào)將會被調(diào)用,到那個(gè)時(shí)候默認(rèn)的生命周期處理器就會檢查每個(gè)SmartLifecycle
對象的isAutoStartup()
方法返回的boolean值偶宫,如果是"true",然后對象就會啟動而不是等待context或自身的start()方法的顯式調(diào)用(不同于context刷新非迹,context的啟動在標(biāo)準(zhǔn)的context實(shí)現(xiàn)中不會發(fā)生).“階段”值以及任何“依賴”關(guān)系將以與上述相同的方式確定啟動順序.
在非web應(yīng)用中優(yōu)雅的關(guān)閉Spring IoC容器
這個(gè)章節(jié)只針對非web應(yīng)用.Spring 基于web的
ApplicationContext
實(shí)現(xiàn)已經(jīng)有代碼去優(yōu)雅的關(guān)閉Spring IoC容器,當(dāng)相關(guān)web應(yīng)用關(guān)閉的時(shí)候
如果你在非web環(huán)境中使用Spring的IoC容器;比如纯趋,你在一個(gè)富客戶端桌面環(huán)境.你使用JVM注冊了一個(gè)關(guān)閉(shutdown)的鉤子(hook).這樣做可以確保優(yōu)雅的關(guān)閉且調(diào)用單例bean相關(guān)的銷毀方法釋放bean它持有的所有資源.當(dāng)然了憎兽,你必須正確配置和實(shí)現(xiàn)這些銷毀方法.
去注冊一個(gè)關(guān)閉鉤子,你可以調(diào)用ConfigurableApplicationContext
接口的registerShutdownHook()
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
1.6.2ApplicationContextAware 和 BeanNameAware
當(dāng)一個(gè)ApplicationContext
創(chuàng)建一個(gè)實(shí)現(xiàn)了org.springframework.context.ApplicationContextAware
接口的對象實(shí)例時(shí)吵冒,這個(gè)實(shí)例就會包含一個(gè)ApplicationContext
的引用.
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
這樣bean就可以編程操作創(chuàng)建它們的ApplicationContext
,通過ApplicationContext
接口或者轉(zhuǎn)型為這個(gè)接口的已知子類比如ConfigurableApplicationContext
,包含同樣的功能.一個(gè)用途是檢索其他beans.有時(shí)候這個(gè)功能是很有用的.然而纯命,你通常你要避免使用它,因?yàn)檫@樣你的代碼會耦合Spring并且不符合控制反轉(zhuǎn)的風(fēng)格桦锄,在協(xié)作類作為bean的熟悉的時(shí)候.ApplicationContext
提供了訪問文件資源,發(fā)布應(yīng)用事件蔫耽,訪問MessageSource
的其他方法.這些附加功能在ApplicationContext的附加功能有描述
從Spring2.5開始结耀,自動裝配是獲取ApplicationContext
的另外一種途徑."傳統(tǒng)" constructor
和byType
自動裝配模式(如自動裝配協(xié)作類中所述)可以分別為ApplicationContext
構(gòu)造函數(shù)參數(shù)或setter方法參數(shù)提供類型的依賴關(guān)系.更靈活的做法留夜,包括自動注入字段和多參數(shù)方法,使用新的基于注解的功能.你如果這樣做,如果需要使用ApplicationContext
類型的字段图甜,方法碍粥,構(gòu)造函數(shù)參數(shù)或方法參數(shù)有@Autowired
注解,那么ApplicationContext
則自動裝入到字段黑毅,構(gòu)造函數(shù)參數(shù)或方法參數(shù)中.更多信息嚼摩,查看 @Autowired.
當(dāng)ApplicationContext
創(chuàng)建一個(gè)實(shí)現(xiàn)org.springframework.beans.factory.BeanNameAware
接口的類時(shí),這個(gè)類提供一個(gè)關(guān)聯(lián)這個(gè)對象定所義的名稱的引用.
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
這個(gè)回調(diào)調(diào)用是在bean的正常屬性注入之后矿瘦,初始化方法回調(diào)比如InitializingBean
的afterPropertiesSet或自定義的初始化方法調(diào)用之前.
1.6.3其他Aware接口
名稱 | 注入依賴 | 在..說明 |
---|---|---|
ApplicationContextAware | 定義ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | ApplicationContext關(guān)閉的事件發(fā)布者 | Additional capabilities of the ApplicationContext |
BeanClassLoaderAware | 用于加載Bean類的類加載器 | Instantiating beans |
BeanFactoryAware | 定義一個(gè)BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | 定義bean的名稱 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | BootstrapContext容器運(yùn)行的資源適配器枕面。通常僅在JCA中可用 | JCA CCI |
LoadTimeWeaverAware | 定義一個(gè)weaver處理類定義在加載的時(shí)候 | Load-time weaving with AspectJ in the Spring Framework |
MessageSourceAware | 配置處理消息的策略 | Additional capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX通知發(fā)布者 | Notifications |
ResourceLoaderAware | 配置一個(gè)加載器對資源的低級別訪問 | Resources |
ServletConfigAware | 當(dāng)前運(yùn)行容器的ServletConfig.只在web應(yīng)用中有效 | Spring MVC |
ServletContextAware | 當(dāng)前運(yùn)行容器的ServletContext.只在web應(yīng)用中有效 | Spring MVC |
除了之前討論的ApplicationContextAware
和BeanNameAware
,Spring還提供了一系列的Aware接口允許bean告訴容器它們需要一定的基礎(chǔ)設(shè)施依賴.
最重要的Aware接口總結(jié)如下(上面一模一樣的列表是簡書上的bug) - 作為一般規(guī)則,名稱是依賴類型的良好指示
表四.Aware接口
名稱 | 注入依賴 | 在..說明 |
---|---|---|
ApplicationContextAware | 定義ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | ApplicationContext關(guān)閉的事件發(fā)布者 | Additional capabilities of the ApplicationContext |
BeanClassLoaderAware | 用于加載Bean類的類加載器 | Instantiating beans |
BeanFactoryAware | 定義一個(gè)BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | 定義bean的名稱 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | BootstrapContext容器運(yùn)行的資源適配器缚去。通常僅在JCA中可用 | JCA CCI |
LoadTimeWeaverAware | 定義一個(gè)weaver處理類定義在加載的時(shí)候 | Load-time weaving with AspectJ in the Spring Framework |
MessageSourceAware | 配置處理消息的策略 | Additional capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX通知發(fā)布者 | Notifications |
ResourceLoaderAware | 配置一個(gè)加載器對資源的低級別訪問 | Resources |
ServletConfigAware | 當(dāng)前運(yùn)行容器的ServletConfig.只在web應(yīng)用中有效 | Spring MVC |
ServletContextAware | 當(dāng)前運(yùn)行容器的ServletContext.只在web應(yīng)用中有效 | Spring MVC |
再次注意潮秘,使用這些接口將您的代碼綁定到Spring API,并且不遵循控制反轉(zhuǎn)風(fēng)格.因此易结,它們被推薦用于需要編程訪問容器的基礎(chǔ)beans中.
1.7Bean定義繼承
一個(gè)bean定義可以包含許多配置信息枕荞,包括構(gòu)造函數(shù)參數(shù),屬性值搞动,和容器的描述信息比如初始化方法躏精,靜態(tài)工廠方法名字等等.一個(gè)子bean定義從一個(gè)父bean定義繼承配置數(shù)據(jù).這些子定義覆蓋相同的值和增加其他值如果需要.使用父和子bean定義可以減少許多輸入內(nèi)容.實(shí)際上,這是一種模板形式.
如果你以編程的方式使用ApplicationContext
,子bean定義可以使用ChildBeanDefinition
表示.大多數(shù)用戶不會使用他們在這個(gè)級別鹦肿,而是用聲明地配置bean定義就像ClassPathXmlApplicationContext一樣.當(dāng)使用基于XML的元數(shù)據(jù)配置時(shí)矗烛,你可以指定一個(gè)child bean定義通過使用parent屬性,描述它的parent bean作為屬性值:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
如果子bean定義不指定bean class就使用父bean定義的狮惜,但是可以覆蓋.在后一種情況高诺,子bean class必須兼容父bean,也就是說,它必須接受父bean的屬性值.
一個(gè)子bean會繼承作用域碾篡,構(gòu)造函數(shù)參數(shù)虱而,屬性值,和覆寫方法從父bean中开泽,并且可以添加新的值.你設(shè)置任何作用域牡拇,初始化方法,銷毀方法穆律,和靜態(tài)工廠方法都會覆蓋父類的設(shè)置.
其余的設(shè)置來自子bean: 依賴關(guān)系惠呼,自動裝配模式,依賴檢查峦耘,單例剔蹋,延遲加載.
之前的例子顯式的標(biāo)識父bean定義是abstract通過使用abstract
屬性.如果父bean定義沒有設(shè)置class,只是按需要顯式的標(biāo)識父bean是abstract,像下面一樣:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean是不能實(shí)例化的辅髓,因?yàn)樗遣煌暾牟⑶宜脖伙@式標(biāo)識為abstract.當(dāng)一個(gè)bean定義像這樣是abstract的泣崩,它只是純粹的bean定義模板少梁,作為子定義的父定義。試圖單獨(dú)使用這樣一個(gè)abstract父bean
矫付,將它作為另一個(gè)bean的ref屬性或者顯式調(diào)用getBean()使用父bean的id,將返回一個(gè)錯(cuò)誤.類似地凯沪,容器內(nèi)部的preInstantiateSingletons()方法將忽略定義成abstract的bean定義.
ApplicationContext
默認(rèn)會預(yù)初始化所有單例beans.因此,這非常重要(至少對單例bean來說)如果你有一個(gè)(父)bean定義打算只作為一個(gè)模板买优,并且這個(gè)bean定義設(shè)置了一個(gè)class,你必須設(shè)置它的abstract屬性為true,否則應(yīng)用上下文會預(yù)實(shí)例化這個(gè)abstract bean.
1.8容器擴(kuò)展點(diǎn)
通常妨马,一個(gè)應(yīng)用開發(fā)者不需要實(shí)現(xiàn)ApplicationContext
的子類.相反,Spring IoC容器可以通過插入特別集成接口實(shí)現(xiàn)來擴(kuò)展.接下來的章節(jié)將會講解這些集成接口.
1.8.1自定義beans使用BeanPostProcessor
BeanPostProcessor
接口定義了回調(diào)方法杀赢,你可以實(shí)現(xiàn)自己的實(shí)例化邏輯(或覆蓋容器默認(rèn)的)烘跺,依賴處理邏輯等等.如果你想實(shí)現(xiàn)一些自定義邏輯在容器完成實(shí)例化,配置和初始化一個(gè)bean后葵陵,你可以增加一個(gè)或多個(gè)BeanPostProcessor
實(shí)現(xiàn).
你可以配置多個(gè)BeanPostProcessor
實(shí)例液荸,并且你可以控制這些BeanPostProcessors
執(zhí)行順序通過設(shè)置它的順序(order)屬性.只有你實(shí)現(xiàn)Ordered
接口時(shí)才能設(shè)置這個(gè)屬性.如果你寫自己的BeanPostProcessor
,你一樣需要考慮實(shí)現(xiàn)Ordered
接口.更多詳細(xì)信息,請參閱BeanPostProcessor
和 Ordered
接口的javadocs .請參閱以下關(guān)于編程式注冊BeanPostProcessor
BeanPostProcessor
s對象一個(gè)bean(或?qū)ο?實(shí)例進(jìn)行操作;意思是說脱篙,Spring IoC容器實(shí)例化一個(gè)bean實(shí)例然后BeanPostProcessor
s再進(jìn)行處理.
BeanPostProcessor
s作用于每個(gè)容器.這只有在使用容器層次結(jié)構(gòu)時(shí)才有意義.如果你定義一個(gè)BeanPostProcessor
在一個(gè)容器娇钱,那么它只處理這個(gè)容器里的bean.換句話說,beans定義在一個(gè)容器不會被定義在另一個(gè)容器的BeanPostProcessor
后處理绊困,即使這兩個(gè)容器都是同一層次結(jié)構(gòu)的一部分
要更改實(shí)際的bean定義(即定義bean 的藍(lán)圖)文搂,您需要使用aBeanFactoryPostProcessor
,如 使用BeanFactoryPostProcessor定制配置元數(shù)據(jù)中所述秤朗。
org.springframework.beans.factory.config.BeanPostProcessor
接口包含兩個(gè)回調(diào)方法.當(dāng)這樣的類在容器中注冊為一個(gè)后處理器(post-processor)煤蹭,對于每個(gè)容器創(chuàng)建的bean實(shí)例,后處理器都會在容器初始化方法(如InitializingBean的afterPropertiesSet()之前和其他聲明的init方法)以及任何bean初始化回調(diào)之后被調(diào)用.后處理器可以對bean實(shí)例執(zhí)行任何操作取视,包括完全忽略回調(diào).一個(gè)bean后處理器通常檢查回調(diào)接口硝皂,或者可能用一個(gè)代理包裝一個(gè)bean.一些Spring AOP基礎(chǔ)類實(shí)現(xiàn)一個(gè)bean后處理器為了提供代理包裝邏輯.
ApplicationContext
可以自動檢測任何定義在配置元數(shù)據(jù)且實(shí)現(xiàn)了BeanPostProcessor
接口的beans.ApplicationContext
注冊這些beans作為后處理器以便在后面創(chuàng)建bean的時(shí)候調(diào)用.Bean后處理器可以像任何其他bean一樣部署在容器中.
注意到當(dāng)定義一個(gè)BeanPostProcessor
使用@Bean
工廠方法在一個(gè)配置類,這個(gè)工廠方法的返回類型應(yīng)該是實(shí)現(xiàn)類本身或者至少org.springframework.beans.factory.config.BeanPostProcessor
接口作谭,清楚地表明該bean的后處理器特性.否則稽物,ApplicationContext
在完全創(chuàng)建它之前, 將無法通過類型對其進(jìn)行自動檢測折欠。由于BeanPostProcessor
需要盡早實(shí)例化以適用于上下文中其他bean的初始化贝或,因此這種早期類型檢測非常關(guān)鍵.
編程式注冊BeanPostProcessors
推薦注冊BeanPostProcessors
的方法是自定檢測(想上面所說的一樣),你也可以通過可編程的方式注冊锐秦,調(diào)用ConfigurableBeanFactory
的addBeanPostProcessor
方法.當(dāng)需要在注冊之前評估條件邏輯咪奖,或者甚至跨層次結(jié)構(gòu)中的上下文復(fù)制Bean后處理器時(shí),這會非常有用.但請注意酱床,BeanPostProcessor
s以編程方式添加忽略Ordered
接口芹血。這是注冊順序決定執(zhí)行順序.還要注意楞慈,BeanPostProcessor
通過編程注冊的總是在通過自動檢測注冊的之前進(jìn)行處理淡溯,而不管任何明確的排序
BeanPostProcessors和AOP自動代理
實(shí)現(xiàn)BeanPostProcessor接口的類是特殊的愤惰,并且容器對其進(jìn)行不同的處理.所有的BeanPostProcessor
s和beans的直接引用在啟動的時(shí)候被實(shí)例化,作為ApplicationContext
特別啟動階段的一部分.接下來绷耍,所有BeanPostProcessor
s以已排序的方式注冊并應(yīng)用于容器中的所有其他bean.因?yàn)锳OP自動代理是作為一個(gè)BeanPostProcessor
方式實(shí)現(xiàn)的吐限,BeanPostProcessors
和它們直接引用的bean都不適合自動代理,因此沒有編入它們的方面.
對于任何這樣的bean褂始,您應(yīng)該看到一條信息性日志消息:“ Bean foo不適合通過所有BeanPostProcessor接口進(jìn)行處理(例如:不適合自動代理) ”
請注意诸典,如果您的bean已連接到BeanPostProcessor使用自動裝配或 @Resource(可能會回退到自動裝配),Spring在進(jìn)行類型匹配查找時(shí)可能會訪問到與預(yù)期不符的beans,,因此使得它們不適合作為自動代理或其它類型的bean 后處理器.比如崎苗,如果你有一個(gè)依賴使用@Resource
注解并且字段或者setter的名字與bean的名字不一致也不使用name屬性搂赋,然后Spring將會訪問其他beans通過類型匹配.
下面的例子展示了在ApplicationContext
如何編寫,注冊和使用BeanPostProcessor
s
例如:Hello World益缠,BeanPostProcessor風(fēng)格
第一個(gè)例子說明了基本用法.這個(gè)例子展示了一個(gè)自定義BeanPostProcessor
實(shí)現(xiàn),調(diào)用容器創(chuàng)建的每個(gè)bean的toString()方法并把字符串結(jié)果打印到系統(tǒng)控制臺.
在自定義BeanPostProcessor
實(shí)現(xiàn)類定義下面查找:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean (messenger) is instantiated, this custom
BeanPostProcessor implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
注意InstantiationTracingBeanPostProcessor
如何簡單定義基公。它甚至沒有名稱幅慌,因?yàn)樗且粋€(gè)bean,它可以像其他任何bean一樣依賴注入轰豆。(前面的配置也定義了一個(gè)由Groovy腳本支持的bean胰伍。Spring動態(tài)語言支持在標(biāo)題為動態(tài)語言支持的章節(jié)中有詳細(xì)介紹 。)
下面簡單的java應(yīng)用執(zhí)行前面的代碼和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
前面的應(yīng)用程序的輸出類似于以下內(nèi)容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:RequiredAnnotationBeanPostProcessor
使用回調(diào)接口或者注解結(jié)合自定義的BeanPostProcessor
是實(shí)現(xiàn)擴(kuò)展Spring∷嵝荨IoC容器的常見方法.一個(gè)例子Spring的RequiredAnnotationBeanPostProcessor
是一個(gè)隨Spring發(fā)布版一起發(fā)布的 BeanPostProcessor
實(shí)現(xiàn)骂租,它確保Bean包含有注釋(任意)的JavaBean屬性(配置為)依賴注入一個(gè)值
1.8.2使用BeanFactoryPostProcessor自定義元數(shù)據(jù)配置
我們將看到下一個(gè)擴(kuò)展點(diǎn)是org.springframework.beans.factory.config.BeanFactoryPostProcessor
.這個(gè)接口的語義與BeanPostProcessor
相似,一個(gè)主要的區(qū)別是:BeanFactoryPostProcessor
對bean配置元數(shù)據(jù)進(jìn)行操作;因此斑司,Spring IoC容器允許一個(gè)BeanFactoryPostProcessor
讀取配置元數(shù)據(jù)渗饮,并可能在容器實(shí)例化除BeanFactoryPostProcessor
s任何beans之前修改它.
你可以配置多個(gè)BeanFactoryPostProcessor
s,并且可以控制這些BeanFactoryPostProcessor
s的執(zhí)行順序通過設(shè)置它的order屬性.然而,只有你實(shí)現(xiàn)了Ordered
接口才能設(shè)置這個(gè)屬性.如果你實(shí)現(xiàn)自己的BeanFactoryPostProcessor
,你需要考慮實(shí)現(xiàn)一個(gè)Ordered
接口.參考BeanFactoryPostProcessor
和Ordered
的javadocs獲取更多詳細(xì)信息.
如果您想更改實(shí)際的bean 實(shí)例(即從配置元數(shù)據(jù)創(chuàng)建的對象)宿刮,則需要使用
BeanPostProcessor
(如上所述互站,使用BeanPostProcessor定制bean)。雖然技術(shù)上可以在一個(gè)BeanFactoryPostProcessor
(例如使用BeanFactory.getBean()
)bean中使用bean實(shí)例僵缺,但這樣做會導(dǎo)致bean過早實(shí)例化胡桃,從而違反標(biāo)準(zhǔn)容器生命周期。這可能會導(dǎo)致負(fù)面影響磕潮,如繞過bean后處理.
同樣翠胰,BeanFactoryPostProcessors
作用于每個(gè)容器.這只有在使用容器層次結(jié)構(gòu)時(shí)才有意義.如果BeanFactoryPostProcessor
定義在一個(gè)容器,那么它只作用于定義于這個(gè)容器里的bean.bean定義在一個(gè)容器中不會被其他容器的BeanFactoryPostProcessors
處理自脯,即使這兩個(gè)容器是同樣層次結(jié)構(gòu)的一部分.
一個(gè)bean工廠后處理器定義在ApplicationContext
會被自動執(zhí)行之景,以便運(yùn)用于修改定義在容器中的元數(shù)據(jù)配置.Spring包含一系列預(yù)定義的bean工廠后處理器,比如PropertyOverrideConfigurer
和PropertyPlaceholderConfigurer
.一個(gè)自定義的BeanFactoryPostProcessor
一樣可以使用膏潮,比如注冊一個(gè)自定義的屬性編輯器.
ApplicationContext
可以自動檢測任何實(shí)現(xiàn)BeanFactoryPostProcessor
接口的bean.容器把這些bean當(dāng)作bean工廠后處理器在適當(dāng)?shù)臅r(shí)候.您可以像任何其他bean一樣部署這些后處理器bean.
就像
BeanPostProcessor
s一樣,你不能配置BeanFactoryPostProcessors
延遲初始化.如果沒有其他bean引用Bean(Factory)PostProcessor闺兢,那么后處理器根本就不會被實(shí)例化.因此,把它標(biāo)記為延遲初始化就會被忽略,Bean(Factory)PostProcessor將會急切初始化即使你設(shè)置<beans />的default-lazy-init屬性為為true.
示例:類名替換PropertyPlaceholderConfigurer
你可以使用PropertyPlaceholderConfigurer
去將bean定義屬性值外部化到一個(gè)單獨(dú)的標(biāo)準(zhǔn)java屬性文件.這樣做屋谭,允許部署應(yīng)用程序的人員可以自定義特定于環(huán)境的屬性脚囊,如數(shù)據(jù)庫URL和密碼,而無需修改容器的主XML定義文件或文件的復(fù)雜性或風(fēng)險(xiǎn).
思考下面基于XML元數(shù)據(jù)配置片段桐磁,其中DataSource定義了一下占位值.下面的例子展示了屬性配置從一個(gè)外部屬性文件.運(yùn)行時(shí)悔耘,PropertyPlaceholderConfigurer
被用于元數(shù)據(jù)替換DataSource的一些屬性.要替換的值被指定為遵循Ant / log4j / JSP EL樣式的表單的占位符${property-name}
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
實(shí)際值來自標(biāo)準(zhǔn)Java Properties格式的另一個(gè)文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,字符串 ${jdbc.username}在允許時(shí)就被替換成'sa',同樣其他占位符值也會匹配屬性文件的key.PropertyPlaceholderConfigurer
會檢查占位符在大多數(shù)properties 和在bean定義中的屬性.此外我擂,占位符的前綴和后綴可以自定義.
使用Spring 2.5中引入的上下文命名空間衬以,可以使用專用配置元素配置屬性占位符.。一個(gè)或多個(gè)文件路徑用逗號分隔作為location屬性.
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
不僅僅從你配置的配置文件中查找屬性.默認(rèn)情況下校摩,它同樣會查找Java System屬性如果在描述的配置文件中找不到對應(yīng)屬性值的時(shí)候.你可以自定義這種行為通過設(shè)置systemPropertiesMode屬性看峻,這個(gè)屬性支持以下三個(gè)整數(shù)值:
- never (0):從不檢查系統(tǒng)屬性
- fallback (1): 檢查系統(tǒng)屬性如果找不到指定屬性值.這是默認(rèn)的
-
override (2): 首先檢查系統(tǒng)屬性,在檢查配置文件之前.這允許系統(tǒng)配置去覆蓋其他配置源.
參考PropertyPlaceholderConfigurer
javadocs獲取更多信息.
例如:PropertyOverrideConfigurer
略
1.8.3使用FactoryBean自定義實(shí)例化邏輯
為自己工廠的對象實(shí)現(xiàn)org.springframework.beans.factory.FactoryBean接口.
FactoryBean
接口是Spring IoC容器實(shí)例化邏輯的擴(kuò)展點(diǎn).如果你有復(fù)雜的初始化代碼衙吩,使用java代碼來實(shí)現(xiàn)更好互妓,而不是冗長的XML,你可以實(shí)現(xiàn)自己的FactoryBean
坤塞,編寫復(fù)雜的初始化代碼在這個(gè)類里面冯勉,然后把你自定義的FactoryBean
插入到容器中.
FactoryBean
接口提供了三個(gè)方法:
- Object getObject(): 返回這個(gè)工廠創(chuàng)建的一個(gè)實(shí)例.這個(gè)實(shí)例是否共享,取決于工廠返回的是單例還是原型
- boolean isSingleton():如果
FactoryBean
返回的是單例摹芙,這個(gè)方法返回是true灼狰,否則返回false. - Class getObjectType(): 返回getObject()方法返回對象的類型或者null如果之前未知的話
這個(gè)FactoryBean
概念和接口在Spring框架的許多地方都有使用; Spring本身提供超過50多個(gè)FactoryBean
的接口實(shí)現(xiàn).
當(dāng)你向容器請求一個(gè)實(shí)際FactoryBean
實(shí)例而不是容器產(chǎn)生的bean,調(diào)用ApplicationContext
的getBean()方法時(shí)bean的id前面加個(gè)&.比如給定的FactoryBean
id是myBean,調(diào)用getBean("myBean")事容器返回的是FactoryBean產(chǎn)生的實(shí)例浮禾,然而調(diào)用getBean("&myBean")交胚,返回的是FactoryBean實(shí)例本身.(ps:FactoryBean也是一個(gè)bean,只不過它是工廠bean,用來組裝生成我們需要的bean盈电,我們一般不直接使用FactoryBean)
1.9基于注解的容器配置
配置Spring使用注解比XML好?
引入基于注解的配置產(chǎn)生一個(gè)問題這種方式是否比XML更好.簡單回答是看情況.詳細(xì)點(diǎn)的話每個(gè)方法都有優(yōu)缺點(diǎn)承绸,通常由開發(fā)者決定使用那種方式.
由于他們定義的方式,注解提供大量的上下文在他們的聲明中,從而使得配置更短,更簡潔.但是肝劲,XML在連接組件時(shí)不需要涉及源碼或者重新編譯.
一些開發(fā)人員更喜歡連接源代碼醉者,而另一些開發(fā)人員則認(rèn)為注釋類不再是POJO,而且配置變得分散,難以控制
不管怎么選擇,Spring可以支持兩種風(fēng)格甚至混合使用他們.值得指出的是通過JavaConfig選項(xiàng),Spring允許注解通過非入侵的方式使用摩幔,
而不需要接觸目標(biāo)組件的源碼,并且在工具方面鞭铆,Spring Tool Suite支持所有配置樣式
基于注釋的配置提供了XML設(shè)置的替代方法或衡,該配置依賴字節(jié)碼元數(shù)據(jù)來連接組件而不是角括號聲明.不同于使用XML去描述一個(gè)bean的連接焦影,開發(fā)者通過在相關(guān)類,方法封断,字段上添加注解把配置移到組件類本身中.正如前面所提到的 例子: The RequiredAnnotationBeanPostProcessor斯辰,使用BeanPostProcessor
結(jié)合注解擴(kuò)展Spring IoC 容器.例如,Spring 2.0引入了使用@Required注解標(biāo)識屬性是必須的坡疼。Spring 2.5使得遵循相同的通用方法來驅(qū)動Spring的依賴注入成為可能彬呻。本質(zhì)上來說,@Autowired
注釋提供了與Autowiring協(xié)作者中描述的功能相同的功能柄瑰,但具有更細(xì)致的控制和更廣泛的適用性闸氮。Spring 2.5還增加了對JSR-250注釋的支持,例如 @PostConstruct
和@PreDestroy
教沾。Spring 3.0增加了對javax.inject包(例如@Inject
和)中包含的JSR-330(依賴注入Java)注釋的支持@Named
蒲跨。有關(guān)這些注釋的詳細(xì)信息可以在相關(guān)部分找到
注解注入比XML注入先執(zhí)行,這樣后者的配置會覆蓋前者的值
像往常一樣授翻,您可以將它們注冊為單獨(dú)的bean定義或悲,但也可以隱式注冊它們通過在基于XML的Spring配置中包含以下標(biāo)記來(注意包含context名稱空間):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
(這些隱式注冊的后處理器包括AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
,
PersistenceAnnotationBeanPostProcessor
,還有之前提到的RequiredAnnotationBeanPostProcessor
)
<context:annotation-config/> 只會查找它所定義的上下文里的bean的注解.意味著,如果你把<context:annotation-config/>放置在
DispatcherServlet
的WebApplicationContext
中藏姐,它只會檢查controllers的@Autowired
bean,而不會檢查services.更多信息查看DispatcherServlet
1.9.1@Required
略
1.9.2 @Autowired
JSR 330的
@Inject
注解可以用來替換Spring的@Autowired
注解在下面的例子中.查看這里獲取更多信息.
你可以使用@Autowired
注解在構(gòu)造函數(shù)上:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
從Spring4.3開始该贾, 如果目標(biāo)bean只定義了一個(gè)構(gòu)造函數(shù)羔杨,
@Autowired
注解在這樣的構(gòu)造函數(shù)中不是必須的.
正如預(yù)期的那樣,您還可以將@Autowired注釋應(yīng)用于“傳統(tǒng)”setter方法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
你還可以把注解用于任意名字杨蛋,包含多個(gè)參數(shù)的方法里:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你還可以把@Autowired
放在字段上甚至混合構(gòu)造函數(shù)一起使用:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
略
可以從ApplicationContext
獲取指定類型的所有bean兜材,通過把@Autowired
注解加在指定類型的數(shù)組字段或者方法上:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
這同樣適用于集合類型:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果你希望數(shù)組中的bean按照指定的順序排序,你的目標(biāo)bean可以實(shí)現(xiàn)
org.springframework.core.Ordered
接口或者使用@Order
或者標(biāo)準(zhǔn)@Priority
注解.否則他們就按照目標(biāo)bean定義注冊到容器順序排序.
@Order
注解可以定義在目標(biāo)class上也可以定義在@Bean
方法上逞力,對于每個(gè)bean定義都是非常獨(dú)立的.@Order
值可能會影響注入點(diǎn)的優(yōu)先級曙寡,但是需要注意的是它不影響單例bean的啟動順序,
這是由依賴關(guān)系和@DependsOn聲明共同確定的.
甚至Map類型也可以自動注入寇荧,如果它的key是String類型.Map會保存所有預(yù)期類型的bean举庶,它的key保存的是bean的名稱.
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默認(rèn)情況下,當(dāng)沒有匹配的bean時(shí)自動裝配就會失敗;默認(rèn)的行為是被注解的方法揩抡,構(gòu)造函數(shù)户侥,字段認(rèn)為是必須依賴.這個(gè)行為可以改變,如下所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
每個(gè)類只有一個(gè)帶注釋的構(gòu)造函數(shù)可以標(biāo)記為必需峦嗤,但可以注釋多個(gè)不需要的構(gòu)造函數(shù)蕊唐。在這種情況下,每個(gè)人都被認(rèn)為是候選人烁设,而Spring使用可以滿足其依賴性的最貪婪的構(gòu)造函數(shù)替梨,即具有最多參數(shù)的構(gòu)造函數(shù)。
建議在@Autowired
中使用required屬性。required屬性表示該屬性不需要自動裝配副瀑,如果它不能自動裝配這個(gè)屬性會被忽略弓熏。另一方面@Required
則更強(qiáng)大,因?yàn)樗鼜?qiáng)制容器給這個(gè)屬性設(shè)置一個(gè)值俗扇。如果沒有值被注入硝烂,則會引發(fā)相應(yīng)的異常
或者,您可以通過Java 8來表達(dá)特定依賴項(xiàng)的非必需性質(zhì)java.util.Optional
:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
從Spring5.0開始铜幽,你可以使用@Nullablepublic class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
你可以使用@Autowired
注入一些常見的接口依賴:BeanFactory
,ApplicationContext
,Environment
, ResourceLoader
, ApplicationEventPublisher
, 和 MessageSource
.這些接口和它們的擴(kuò)展接口滞谢,比如ConfigurableApplicationContext
或 ResourcePatternResolver
會自動解析,不需要特別的設(shè)置.
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
,@Inject
,@Resource
, 和@Value
這些注解是被Spring的BeanPostProcessor
實(shí)現(xiàn)來處理的除抛,反過來意味著你不能在你的BeanPostProcessor
或BeanFactoryPostProcessor
類(如果有的話)使用這些注解..這些類型顯示的連接使用XML或@Bean
方法.
使用@Primary微調(diào)基于注解的自動裝配
因?yàn)樽詣友b配會導(dǎo)致多個(gè)候選匹配狮杨,這就需要更多的控制在選擇的過程.一個(gè)實(shí)現(xiàn)的方式就是使用Spring的@Primary
注解.@Primary
表示當(dāng)多個(gè)bean可以自動裝配到單例依賴項(xiàng)時(shí),被@Primary
注解的bean優(yōu)先.如果在這些候選中確實(shí)存在一個(gè)'主要'bean到忽,那么就注入這個(gè)值.
假設(shè)我們有下面的配置橄教,其中定義了一個(gè)firstMovieCatalog作為主要的(primary)的MovieCatalog
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
有了這樣的配置,下面的MovieRecommender就會主動注入firstMovieCatalog.
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
相應(yīng)的bean定義如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4使用qualifiers微調(diào)基于注解的自動裝配
按類型進(jìn)行自動裝配有多個(gè)實(shí)例需要確定一個(gè)主要候選的時(shí)候喘漏,@Primary
是個(gè)有效的方式.在選擇過程需要更多控制的時(shí)候护蝶,可以使用Spring的@Qualifier
注解.您可以將限定符值與特定參數(shù)相關(guān)聯(lián),縮小匹配類型的集合翩迈,以便為每個(gè)參數(shù)選擇特定的bean持灰。在最簡單的情況下,這可以是一個(gè)簡單的描述性值:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier
注解同樣適用在單個(gè)構(gòu)造函數(shù)參數(shù)或者方法參數(shù):
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
相應(yīng)的bean定義如下.限定符為main
的bean與有相同限定值構(gòu)造函數(shù)參數(shù)關(guān)聯(lián).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
對于后備匹配负饲,bean的名稱被視為默認(rèn)的限定符值.這樣你可以定義一個(gè)bean id是"main",替代內(nèi)部的限定符元素堤魁,得到的結(jié)果是一樣的.然而,盡管你可以使用這種約定通過名稱來引用指定的bean返十,@Autowired
結(jié)合可以選的限定符是最基本的類型驅(qū)動注入.這意味著限定符值(即使使用bean名稱作為后備)也會在匹配類型集合內(nèi)縮小語義;它們不會在語義上表達(dá)對唯一bean id的引用.好的限定符值如"main"或者"EMEA"或者"persistent",表示所描述的bean組件獨(dú)立于bean id,id也許是自動生成對于匿名的bean定義.
限定符同樣可以使用在集合類型上妥泉,就像前面討論的一樣,比如Set<MovieCatalog>.在這種情況下洞坑,根據(jù)聲明的限定符所有匹配的Bean將作為集合注入.這意味著限定詞不必是唯一的; 它們只是過濾條件.比如你可以定義多個(gè)限定符值為"action"的MovieCatalog beans,這些beans都會注入到被注解為@Qualifier("action")的Set<MovieCatalog>中
在類型匹配候選中盲链,讓限定符值針對目標(biāo)bean名稱進(jìn)行選擇 ,注入點(diǎn)并不需要@Qualifier注解迟杂。如果沒有其他解析指示符(例如限定符或primary標(biāo)記)匈仗,那么對于非唯一的依賴性情況,Spring將匹配注入點(diǎn)名稱(即字段名稱或參數(shù)名稱)與目標(biāo)bean名稱逢慌,并選擇相同的名稱的候選人悠轩,如果有的話。
也就是說攻泼,如果您打算按名稱來進(jìn)行注解驅(qū)動注入火架,不必主要使用@Autowired
鉴象,即使可以在類型匹配的候選中使用bean名稱進(jìn)行選擇。相反何鸡,使用JSR-250@Resource
注解纺弊,該注解在語義上定義標(biāo)識特定目標(biāo)組件通過其唯一名稱,并且聲明的類型與匹配過程無關(guān)骡男。@Autowired
具有相當(dāng)不同的語義:在按類型選擇候選bean之后淆游,指定的字符串限定符值將僅在這些類型選擇的候選者中被考慮。
對于本身被定義為集合/map或數(shù)組類型的bean隔盛,@Resource
是一個(gè)很好的解決方案犹菱,通過唯一名稱引用特定集合或數(shù)組bean。也就是說吮炕,@Autowired只要元素類型信息保留在@Bean返回類型簽名或集合繼承層次結(jié)構(gòu)中腊脱,就可以通過Spring的類型匹配算法來匹配集合/映射和數(shù)組類型 。在這種情況下龙亲,可以使用限定符值在相同類型的集合中進(jìn)行選擇陕凹,如前一段所述。
@Autowired適用于字段鳄炉,構(gòu)造函數(shù)和多參數(shù)方法杜耙,允許通過參數(shù)級別的限定符注釋進(jìn)行縮小。相比之下拂盯,@Resource 僅支持具有單個(gè)參數(shù)的字段和bean屬性設(shè)置方法佑女。因此,如果注入目標(biāo)是構(gòu)造函數(shù)或多參數(shù)方法磕仅,則堅(jiān)持使用限定符
你可以創(chuàng)建自定義的限定符注解.你定義一個(gè)簡單的注解并在注解上添加@Qualifier
注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后你就可以使用自定義的限定符注解在主動注入的字段和參數(shù)上:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下來珊豹,提供候選bean定義的信息.你可以在<bean/>
增加一個(gè)<qualifier/>
元素簸呈,在里面描述類型和值來匹配你自定義的限定符注解.類型匹配自定義注解的全限定類名.或者榕订,如果不存在沖突風(fēng)險(xiǎn),為了方便可以用短類名.下面的例子演示這兩種方法:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在Classpath掃描和管理組件中蜕便,您將看到一種基于注釋的替代方法來提供XML中的限定符元數(shù)據(jù)劫恒。具體來說,請參閱提供限定符元數(shù)據(jù)和注釋轿腺。
在某些情況下两嘴,使用注解不需要值就夠了.當(dāng)注解提供更通用的用途并且可以應(yīng)用于多種不同類型的依賴關(guān)系時(shí),這可能很有用族壳。比如憔辫,你可以提供一個(gè)離線類別去搜索當(dāng)沒有網(wǎng)絡(luò)連接的時(shí)候.首先定義一個(gè)簡單的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后添加注解到將要自動裝配的字段或?qū)傩灾校?/p>
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
現(xiàn)在bean定義只需要一個(gè)限定符類型:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
你可以同樣自定義限定符接受一個(gè)名稱屬性而不是簡單的value屬性.如果多個(gè)屬性值應(yīng)用于自動裝配的字段或者參數(shù),一個(gè)bean定義必須匹配所有屬性值才被考慮為自動裝配候選者.舉個(gè)例子仿荆,思考下面注解定義:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
例子中Format是個(gè)枚舉:
public enum Format {
VHS, DVD, BLURAY
}
要自動裝配的字段使用自定義限定符進(jìn)行注解贰您,并包含兩個(gè)屬性的值:genre和format坏平。
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最終,bean定義需要包含匹配限定符值.這個(gè)例子同時(shí)也演示了meta屬性可以替代子元素<qualifier/>
.如果可用锦亦,<qualifier/>
和它的屬性優(yōu)先舶替,但<meta/>如果不存在此類限定符,則自動裝配機(jī)制會回退標(biāo)簽中提供的值,如以下示例中的最后兩個(gè)bean定義所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>
1.9.5使用泛型作為自動裝配限定符
處理@Qualifier
注解外杠园,還可以使用java的泛型作為隱式的限定符.如下例所示顾瞪,假如你有如下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
加入上述bean實(shí)現(xiàn)一個(gè)通用接口,比如Store<String> 和 Store<Integer>抛蚁,你可以@Autowire
Store接口和它的泛型作為限定符:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
泛型限定符同樣適用于列表陈醒,Map,數(shù)組:
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
1.9.6CustomAutowireConfigurer
CustomAutowireConfigurer
是一個(gè)BeanFactoryPostProcessor
允許你注冊自定義的限定符注解類型篮绿,即使他們不使用@Qualifier
注解.
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver
通過以下方式覺得自動注入的候選人:
- 每個(gè)bean定義的autowire-candidate值
- <beans/>元素中任何可用的default-autowire-candidates模式值
- 包含
@Qualifier
的注解和任何自定義注冊到CustomAutowireConfigurer
的注解
當(dāng)多個(gè)bean被認(rèn)定為自動裝配候選者時(shí)孵延,“primary”的確定如下:如果候選者中恰好有一個(gè)bean定義primary 屬性設(shè)置為true,則它將被選中亲配。
1.9.7@Resource
Spring 注入同樣支持使用JSR-250@Resource
注解在字段或者bean屬性的setter方法.這是一個(gè)通用的模式在Java EE 5 and 6.例如在JSF 1.2托管bean或JAX-WS 2.0端點(diǎn)中尘应。Spring也支持這種Spring管理對象的模式.
@Resource
需要一個(gè)name屬性,Spring將該值解釋為要注入的bean名稱.換句話說吼虎,它的語意是按名稱注入犬钢,下面的例子所說的:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果沒有明確指定名稱,則默認(rèn)名稱是從字段名稱或setter方法派生的.在字段名稱上思灰,它使用字段名稱.
在方法上玷犹,它使用bean屬性名稱.所以下面的例子將把名為“movieFinder”的bean注入到它的setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
name解析成bean的名稱是由
ApplicationContext
的CommonAnnotationBeanPostProcessor
來完成.名稱解析可以通過JNDI如果你配置了Spring的SimpleJndiBeanFactory
.然而,推薦你使用默認(rèn)的行為并簡單地使用Spring的JNDI查找功能來保留間接級別洒疚。
在沒有指定明確名稱的情況下使用@Resource歹颓,這有點(diǎn)類似于@Autowired
,@Resource
查找主類型匹配而不是一個(gè)具體的bean并解決眾所周知的解析依存關(guān)系:BeanFactory,
ApplicationContext
, ResourceLoader
, ApplicationEventPublisher
, 和 MessageSource
接口.
這樣,在下面的例子中油湖,customerPreferenceDao字段首先查找一個(gè)bean名字為customerPreferenceDao,然后退而求其次主類型匹配查找類型CustomerPreferenceDao. "context" 字段被注入ApplicationContext
.
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
1.9.8@PostConstruct and @PreDestroy
CommonAnnotationBeanPostProcessor
不僅能識別@Resource
注解還能識別JSR-250生命周期注解.在Spring 2.5中引入的對這些注釋的為初始化回調(diào)函數(shù)和 銷毀回調(diào)函數(shù)提供了另一種替代方案.實(shí)現(xiàn)識別這些生命周期注解的CommonAnnotationBeanPostProcessor
注冊在Spring的ApplicationContext
,帶有這些注解其中一個(gè)的方法會被調(diào)用巍扛,與Spring生命周期接口方法或顯式定義的回調(diào)方法對應(yīng).在下面的示例中,緩存將在初始化時(shí)預(yù)填充乏德,并在銷毀時(shí)清除撤奸。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
1.10類路徑掃描和組件管理
在這個(gè)章節(jié)中大多數(shù)例子使用XML去描述Spring容器生成每個(gè)BeanDefinition
的配置元數(shù)據(jù).在之前的章節(jié) (基于注解的容器配置)演示了如何通過源代碼級注解提供大量配置元數(shù)據(jù).但是,即使在這些示例中喊括,“基本”bean定義在XML文件中顯式定義胧瓜,而注解用于依賴注入.本節(jié)介紹一種可選方法用于隱式檢測候選組件通過掃描類路徑.候選組件是與過濾條件相匹配的類,并在容器中注冊相應(yīng)的bean定義郑什。這就消除了使用XML來執(zhí)行bean的注冊;相反你使用注解(比如@Component
)府喳,AspectJ類型表達(dá)式,或者你自定義的過濾條件去選擇在容器中注冊bean定義的類.
從Spring3.0開始蘑拯,Spring JavaConfig項(xiàng)目提供的許多功能都是Spring核心框架的一部分.這允許你定義bean使用java而不是傳統(tǒng)的XML文件.看看
@Configuration
钝满,@Bean
肉津,@Import
,和@DependsOn
注解有關(guān)的例子舱沧,它們是如何使用這些新功能.
1.10.1 @Component和更原型的注解
@Repository
注解通常標(biāo)識一個(gè)類的作用是repository(也被稱為數(shù)據(jù)訪問對象或DAO).這個(gè)標(biāo)記的用途是自動翻譯異常就像異常翻譯所描述的一樣
Spring提供了更原型的注解:@Component
, @Service
, 和 @Controller
.@Component
是任何Spring管理組件的通用原型.@Repository
, @Service
, 和 @Controller
是定制化的@Component
,用于更具體的場景.比如妹沙,分別在持久化層,業(yè)務(wù)層熟吏,和表示層.因此你可以注解你的組件類使用@Component
,但如果使用@Repository
距糖,@Service
或者@Controller
注解它們,你的類能更好地被工具處理,或與切面進(jìn)行關(guān)聯(lián).比如牵寺,這些原型注解是切入點(diǎn)的理想目標(biāo).@Repository
, @Service
, 和 @Controller
在未來的Spring版本中可能會增加額外的語義.這樣悍引,如果你在服務(wù)層中選擇@Component
或@Service
,很明顯@Service
是明智的選擇.同樣帽氓,如上所述趣斤,@Repository已經(jīng)被支持作為持久層中自動異常轉(zhuǎn)換的標(biāo)記.
1.10.2元注解
Spring提供的很多注解可以作為你代碼的元注解.元注解是一個(gè)可以用于其他注解的簡單的注解.比如,之前提到的@Service
被@Component
元注解.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {
// ....
}
元注釋也可以組合起來創(chuàng)建組合注釋.比如黎休,Spring MVC的@RestController
注解就組合了@Controller
和@ResponseBody
.
此外浓领,組合注解可以重定義元注解的屬性允許用戶自定義.當(dāng)您只想暴露元注解屬性的子集時(shí),這可能特別有用.比如Spring的@SessionScope
硬編碼作用域名字是session但仍然允許自定義proxyMode.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后@SessionScope
直接使用而不需要定義proxyMode,如下所示:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
或者覆蓋proxyMode值势腮,如下所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
更多詳細(xì)信息联贩,查看 Spring注解編程模型
1.10.3自動檢測類并注冊bean定義
Spring可以自動檢測原型類并注冊對應(yīng)的BeanDefinition
s到ApplicationContext.比如,以下兩個(gè)類都是可以自動檢測的:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
為自動檢測這些類并注冊對應(yīng)的bean捎拯,你需要添加@ComponentScan
到你的@Configuration
類泪幌,其中該basePackages屬性是這兩個(gè)類的共同父級包(或者,你可以使用逗號/分號/空格分隔各個(gè)父包)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
為了簡潔署照,上面你可以使用注解的value屬性祸泪,比如@ComponentScan("org.example")
與下面使用XML等價(jià):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
使用<context:component-scan>隱式的包含了<context:annotation-config>的功能,所以使用<context:component-scan>的時(shí)候就沒有必要使用<context:annotation-config> .
類路徑包的掃描要求類路徑中存在相應(yīng)的目錄條目建芙。在使用Ant構(gòu)建JAR時(shí)没隘,請確保不要 激活JAR任務(wù)的僅文件開關(guān)。此外岁钓,在某些環(huán)境中升略,類路徑目錄可能不會基于安全策略公開微王,例如JDK 1.7.0_45和更高版本上的獨(dú)立應(yīng)用程序(這需要在您的清單中設(shè)置“受信任的庫”;請參閱 http://stackoverflow.com/questions/ 19394570 / java-jre-7u45-breaks-classloader-getresources)屡限。
在JDK 9的模塊路徑(Jigsaw)中,Spring的類路徑掃描一般按預(yù)期工作炕倘。但是钧大,請確保您的組件類在您的module-info
描述符中導(dǎo)出; 如果你希望Spring調(diào)用你的類的非公共成員,確保它們是'打開'的(即使用opens
聲明而不是描述符中的exports
聲明module-info
)罩旋。
此外啊央,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
都會隱式包含當(dāng)使用組件掃描(component-scan)元素的時(shí)候.這意味著這兩個(gè)組件會自動檢測和連接在一起而不需要在XML中有任何的bean元數(shù)據(jù)配置.
1.10.4使用過濾器自定義掃描
默認(rèn)情況下眶诈,被@Component
, @Repository
, @Service
, @Controller
或自身被@Component
注解的自定義注解注解的類才會被作為自動檢測的候選組件.然而,你可以很容易修改和擴(kuò)展這些行為通過提供自定義過濾器.添加它們作為@ComponentScan的 includeFilters或 excludeFilters 參數(shù)(或者作為組件掃描元素的子元素 include-filter 或 exclude-filter 的值).每個(gè)過濾器元素需要類型和表達(dá)式屬性.下表描述了過濾器可選項(xiàng).
表5.過濾器類型
過濾器類型 | 表達(dá)式例子 | 描述 |
---|---|---|
注解(annotation)默認(rèn)類型 | org.example.SomeAnnotation | 注解存在目標(biāo)組件類級別上 |
assignable | org.example.SomeClass | 目標(biāo)組件分配的一個(gè)類(或接口(擴(kuò)展或?qū)崿F(xiàn))) |
aspectj | org.example..*Service+ | 一個(gè)AspectJ類型表達(dá)式匹配目標(biāo)組件 |
正則表達(dá)式(regex) | org.example.Default.* | 一個(gè)正則表達(dá)式目標(biāo)組件的類名 |
自定義 | org.example.MyTypeFilter | 自定義實(shí)現(xiàn)org.springframework.core.type .TypeFilter接口 |
下面的例子展示了配置忽略所有@Repository
注解和使用"sub"repositories替代
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
等價(jià)的XML配置如下所示:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
你可以禁用默認(rèn)過濾器通過設(shè)置useDefaultFilters=false在注解上或者設(shè)置<component-scan/> 的屬性use-default-filters="false".這將會關(guān)閉被@Component, @Repository, @Service, @Controller, or @Configuration注解的類的自動檢測
1.10.5使用component定義bean元數(shù)據(jù).
Spring的component同樣可以將bean元數(shù)據(jù)定義提供給容器.你可以使用@Bean注解去定義bean元數(shù)據(jù)與@Configuration
注解類一樣.如下例子所示:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
這個(gè)類是一個(gè)Spring組件瓜饥,它的doWork()方法中包含了特定于應(yīng)用程序的代碼.然而逝撬,它同樣提供了bean定義,該定義涉及到一個(gè)工廠方法publicInstance().@Bean
注解標(biāo)識工廠方法和其他bean定義屬性乓土,比如通過@Qualifier
設(shè)置限定符值.其他可用的方法級注解是@Scope
,@Lazy
和自定義限定符注解.
略
就像前面所說的一樣宪潮,自動裝配字段和方法同樣支持,還支持自動注入@Bean
方法:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
這個(gè)例子中字符串方法參數(shù)country的值注入的是另一個(gè)名為privateInstance bean的age 屬性.Spring表達(dá)式語言元素通過符號#{ <expression> }定義屬性的值.對于@Value
注解趣苏,一個(gè)表達(dá)式解析器被預(yù)先配置查找bean的名稱在解析表達(dá)式文本的時(shí)候(ps:@Value(${})注入的是配置文件的內(nèi)容).
從Spring框架4.3開始狡相,你可以定義一個(gè)工廠方法,參數(shù)類型是InjectionPoint
(或者更具體的子類DependencyDescriptor
)以便訪問觸發(fā)創(chuàng)建當(dāng)前bean的請求注入點(diǎn).注意到這個(gè)只運(yùn)用于實(shí)際創(chuàng)建bean的實(shí)例食磕,而不是注入已存在的實(shí)例.因此尽棕,這個(gè)功能大多數(shù)的使用場景是原型bean.對于其它作用域,工廠方法只會看到觸發(fā)創(chuàng)建新bean實(shí)例的注入點(diǎn)在給定的作用域:比如彬伦,依賴觸發(fā)創(chuàng)建延遲加載的單例bean.在這種情況下使用提供的注入點(diǎn)元數(shù)據(jù)和語義保護(hù):
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}