Spring IoC 容器

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.beansorg.springframework.context兩個(gè)包是實(shí)現(xiàn)spring IoC功能最基礎(chǔ)的兩個(gè)包.BeanFactory接口提供一種能夠管理任何類型對象的高級配置機(jī)制.ApplicationContextBeanFactory的子接口.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)用程序.

The Spring IoC container

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定義


    The bean definition

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 beanautowiring 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è)BarBaz與繼承無關(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/>元素的值在ProxyFactoryBeanbean定義的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都返回緩存中的對象.


image.png

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.


image.png

下面的例子在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的InitializingBeanDisposableBean接口.容器調(diào)用InitializingBeanafterPropertiesSet()方法在bean完成初始化后坐搔,調(diào)用DisposableBeandestroy()在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代碼的配置中腹尖,你可以使用@BeaninitMethod屬性,查看 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代碼的配置中绎巨,你可以使用@BeandestroyMethod屬性,查看 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提供的InitializingBeanDisposableBean接口方法時(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)閉中诀姚,所有Lifecyclebeans首先會收到停止同知在銷毀回調(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)SmartLifecycleLifecycle對象的默認(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)啟用了SmartLifecyclebean的另外一個(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)" constructorbyType自動裝配模式(如自動裝配協(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

除了之前討論的ApplicationContextAwareBeanNameAware,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ì)信息,請參閱BeanPostProcessorOrdered接口的javadocs .請參閱以下關(guān)于編程式注冊BeanPostProcessor

BeanPostProcessors對象一個(gè)bean(或?qū)ο?實(shí)例進(jìn)行操作;意思是說脱篙,Spring IoC容器實(shí)例化一個(gè)bean實(shí)例然后BeanPostProcessors再進(jìn)行處理.
BeanPostProcessors作用于每個(gè)容器.這只有在使用容器層次結(jié)構(gòu)時(shí)才有意義.如果你定義一個(gè)BeanPostProcessor在一個(gè)容器娇钱,那么它只處理這個(gè)容器里的bean.換句話說,beans定義在一個(gè)容器不會被定義在另一個(gè)容器的BeanPostProcessor后處理绊困,即使這兩個(gè)容器都是同一層次結(jié)構(gòu)的一部分
要更改實(shí)際的bean定義(即定義bean 的藍(lán)圖)文搂,您需要使用a BeanFactoryPostProcessor,如 使用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)用ConfigurableBeanFactoryaddBeanPostProcessor方法.當(dāng)需要在注冊之前評估條件邏輯咪奖,或者甚至跨層次結(jié)構(gòu)中的上下文復(fù)制Bean后處理器時(shí),這會非常有用.但請注意酱床,BeanPostProcessors以編程方式添加忽略Ordered接口芹血。這是注冊順序決定執(zhí)行順序.還要注意楞慈,BeanPostProcessor通過編程注冊的總是在通過自動檢測注冊的之前進(jìn)行處理淡溯,而不管任何明確的排序

BeanPostProcessors和AOP自動代理
實(shí)現(xiàn)BeanPostProcessor接口的類是特殊的愤惰,并且容器對其進(jìn)行不同的處理.所有的BeanPostProcessors和beans的直接引用在啟動的時(shí)候被實(shí)例化,作為ApplicationContext特別啟動階段的一部分.接下來绷耍,所有BeanPostProcessors以已排序的方式注冊并應(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如何編寫,注冊和使用BeanPostProcessors

例如: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í)例化除BeanFactoryPostProcessors任何beans之前修改它.
你可以配置多個(gè)BeanFactoryPostProcessors,并且可以控制這些BeanFactoryPostProcessors的執(zhí)行順序通過設(shè)置它的order屬性.然而,只有你實(shí)現(xiàn)了Ordered接口才能設(shè)置這個(gè)屬性.如果你實(shí)現(xiàn)自己的BeanFactoryPostProcessor,你需要考慮實(shí)現(xiàn)一個(gè)Ordered接口.參考BeanFactoryPostProcessorOrdered的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工廠后處理器,比如PropertyOverrideConfigurerPropertyPlaceholderConfigurer.一個(gè)自定義的BeanFactoryPostProcessor一樣可以使用膏潮,比如注冊一個(gè)自定義的屬性編輯器.
ApplicationContext可以自動檢測任何實(shí)現(xiàn)BeanFactoryPostProcessor接口的bean.容器把這些bean當(dāng)作bean工廠后處理器在適當(dāng)?shù)臅r(shí)候.您可以像任何其他bean一樣部署這些后處理器bean.

就像BeanPostProcessors一樣,你不能配置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)配置去覆蓋其他配置源.
    參考PropertyPlaceholderConfigurerjavadocs獲取更多信息.
例如: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è)&.比如給定的FactoryBeanid是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/>放置在DispatcherServletWebApplicationContext中藏姐,它只會檢查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ò)展接口滞谢,比如ConfigurableApplicationContextResourcePatternResolver會自動解析,不需要特別的設(shè)置.

public class MovieRecommender {

@Autowired
private ApplicationContext context;

public MovieRecommender() {
}

// ...
}

@Autowired, @Inject, @Resource, 和 @Value這些注解是被Spring的BeanPostProcessor實(shí)現(xiàn)來處理的除抛,反過來意味著你不能在你的BeanPostProcessorBeanFactoryPostProcessor類(如果有的話)使用這些注解..這些類型顯示的連接使用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>抛蚁,你可以@AutowireStore接口和它的泛型作為限定符:

@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的名稱是由ApplicationContextCommonAnnotationBeanPostProcessor來完成.名稱解析可以通過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)的BeanDefinitions到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)罩旋。

此外啊央,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都會隱式包含當(dāng)使用組件掃描(component-scan)元素的時(shí)候.這意味著這兩個(gè)組件會自動檢測和連接在一起而不需要在XML中有任何的bean元數(shù)據(jù)配置.

1.10.4使用過濾器自定義掃描

默認(rèn)情況下眶诈,被@Component, @Repository, @Service, @Controller或自身被@Component注解的自定義注解注解的類才會被作為自動檢測的候選組件.然而,你可以很容易修改和擴(kuò)展這些行為通過提供自定義過濾器.添加它們作為@ComponentScan的 includeFiltersexcludeFilters 參數(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());
}
}

(譯自spring框架官方手冊滔悉,用于本人學(xué)習(xí)了解框架提供功能,特性等单绑,持續(xù)更新中氧敢,篇幅較多儒旬,由于個(gè)人能力有限惰许,難免有錯(cuò)漏,希望大家不吝賜教)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吠谢,一起剝皮案震驚了整個(gè)濱河市份氧,隨后出現(xiàn)的幾起案子唯袄,更是在濱河造成了極大的恐慌,老刑警劉巖蜗帜,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恋拷,死亡現(xiàn)場離奇詭異,居然都是意外死亡厅缺,警方通過查閱死者的電腦和手機(jī)蔬顾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來湘捎,“玉大人诀豁,你說我怎么就攤上這事】荆” “怎么了舷胜?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長活翩。 經(jīng)常有香客問我烹骨,道長翻伺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任沮焕,我火速辦了婚禮吨岭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峦树。我一直安慰自己未妹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布空入。 她就那樣靜靜地躺著络它,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歪赢。 梳的紋絲不亂的頭發(fā)上化戳,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音埋凯,去河邊找鬼点楼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛白对,可吹牛的內(nèi)容都是我干的掠廓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼甩恼,長吁一口氣:“原來是場噩夢啊……” “哼蟀瞧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起条摸,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤悦污,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钉蒲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體切端,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年顷啼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了踏枣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钙蒙,死狀恐怖茵瀑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仪搔,我是刑警寧澤瘾婿,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布蜻牢,位于F島的核電站烤咧,受9級特大地震影響偏陪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煮嫌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一笛谦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昌阿,春花似錦饥脑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刷钢,卻和暖如春笋颤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背内地。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工伴澄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阱缓。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓非凌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荆针。 傳聞我的和親對象是個(gè)殘疾皇子敞嗡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)航背,斷路器秸妥,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,311評論 1 92
  • 1- IOC的概念 IOC:也即控制反轉(zhuǎn)沃粗,DI即依賴注入粥惧,控制反轉(zhuǎn)IOC和依賴注入DI其實(shí)就是同個(gè)概念的兩個(gè)不同...
    zhanglbjames閱讀 2,997評論 1 3
  • 蘇州升盛投資有限公司經(jīng)蘇州市政府批準(zhǔn)成立并在蘇州工商行政管理局注冊登記,作為一家專業(yè)的配資咨詢服務(wù)團(tuán)隊(duì)最盅,通過嚴(yán)謹(jǐn)突雪,...
    大栗lz閱讀 401評論 0 0