Spring4參考手冊中文版


Spring4參考手冊中文版

 

前言

 https://github.com/b2gats/stone-docs/blob/master/spring-4-beans.md

Part III. 核心技術

本部分參考手冊完全覆蓋了Srping 框架的全部技術

首先是Spring IoC控制反轉。深入徹底的IoC講解之后,緊隨其后的是全面解說Spring AOP。Spring有自己的AOP框架,該框架概念簡單易於理解,能解決Java企業應用中80%的需求

Spring也集成了AspectJ,AspectJ是現今java領域功能最豐富、最成熟的AOP實現。

最后面的部分是tdd測試驅動開發,也是Spring 開發團隊最為推崇的開發方法,主要內容有單元測試和spirng對集成測試的支持。Spring 團隊發現,正確的使用Ioc,會使單元測試和集成測試更加簡單(因為類中使用Setter和構造函數,將使它們更容易的配合,而無需使用set up組裝)。同時,為測試弄了專門的單獨章節,希望你能領悟這一點

IoC容器

springIOC容器和beans簡介

本章講解spring的控制反轉(IoC)的spring 框架實現 [1] 原理. IoC 又名 依賴注入 (DI). 它是一個由對象定義依賴的處理手法,也就是如何與其他對象協同工作, 可以通過以下途徑定義依賴:構造參數注入、工廠方法的參數注入、屬性注入(是指對象實例化后或者從工廠方法返回一個實例后設置其屬性)。容器創建bean時候, 注入 依賴。 這個控制倒轉了, 因此得名控制反轉 (IoC)。反轉了哪些控制,不再是由bean自己控制依賴類的實例化和定位, 而是使用了類似於 服務定位 模式的機制來控制。

org.springframework.beans 和 org.springframework.context 這兩個包是spring IOC容器的基礎包. BeanFactory 接口 提供了各種配置,用於管理任何對象. ApplicationContext 是 BeanFactory的子接口. 它提供以下功能,使集成Spring’s AOP功能 更容易; 消息資源處理(用於國際化);事件發布;應用層指定上下文環境,像用於web應用的WebApplicationContext.

簡而言之, BeanFactory 提供了配置框架和基礎功能, ApplicationContext 增加了更多的企業應用用能. ApplicationContext是 BeanFactory的超集, 是本章的spring IOC示例中的指定容器. 用BeanFactory 替代 ApplicationContext, 更多的信息請參看 Section 4.17, “The BeanFactory”.

應用中的對象並且是由spring 容器 管理的,被稱為beans.就是對象,由spring容器管理的諸如實例化、組裝等等操作. bean可以由應用中的多個對象組成。Bean通過容器和配置元數據 ,使用反射技術,去組裝依賴對象。

容器概述

接口`org.springframework.context.ApplicationContext`代表了srping IoC 容器,負責實例化、配置和組裝前面提到的beans。容器依據配置配置元數據去實例化、配置、組裝。配置元數據可以用XML、Java 注解、或者Java編碼表示。在配置元數據中,可以定義組成應用的對象,以及對象之間的依賴關系。

Spring 提供了一些開箱即用的ApplicationContext接口的實現。在單獨的應用中,通常使用ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext。當使用XML定義配置元數據時,可通過一小段xml配置使容器支持其他格式的配置元數據,比如Java 注解、Java Code。

大多數的應用場景中,不需要硬編碼來實例化一個Spring IoC 的容器。舉個栗子,web應用中,在web.xml中大概8行左右的配置就可以實例化一個Spring Ioc容器(see Section 4.16.4, “Convenient ApplicationContext instantiation for web applications”)。若再有STS(Spring eclipse 套件),簡單的鈎鈎點點即可完成此配置。

下面的示意圖是spring工作原理。ApplicationContext將應用中的類與配置元數據相結合,實例化后,即可得到一個可配置、可執行的系統或應用。
The Spring IoC container
spring 工作原理示意圖

配置元數據

如上圖所示,Spring IoC容器使用某種格式的配置元數據;配置元數據,就是告訴Ioc容器如何將對象實例化、配置、組裝。

配置元數據是默認使用簡單直觀的xml格式,也是本章樣例中使用最多的,這些樣例程序用以說明spring Ioc 容器的核心概念和功能

注意

基於XML的元數據並不是唯一的格式。Spring IoC容器已經和提及到的元數據格式完全解耦了。目前,很多碼農都選擇Java-based configuration

欲了解其他元數據格式的使用,請參看:

  • Annotation-based configuration: Spring 2.5 引進的支持java 注解配置元數據
  • Java-based configuration: Spring3.0時,將Spring JavaConfig project的很多功能集成到核心Spring框架中。Thus you can define beans external to your application classes by using Java rather than XML files.你可以使用java配置類定義bean,無需xml,該配置類與應用類無關。想要嘗鮮的話,請參看@Configuration,@Bean,@Import,@DependsOn注解

Spring配置由Spring bean的定義組成,這些bean必須被容器管理,至少1個,通常會有多個。基於XML的配置元數據,大概這么配置,根節點<beans>中配置子節點<bean>。Java configuration使用是這樣的,一個帶有@Configuration類注解的類中,方法上使用@Bean方法注解。

bean的定義要與應用中實際的類相一致。可以定義service 層的對象、Dao對象、類似Struts的表現成的對象、像Hibernate SessionFactories這樣的基礎對象,JMS隊列等等。通常不會去定義細粒度域對象,因為它們由DAO或者Service負責創建、加載。然而,通過集成AspectJ,可以配置非Srping容器創建的對象。參看Using AspectJ to dependency-inject domain objects with Spring

下面的樣例展示了基於XML配置元數據的基本格式:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." class="..."> <!-- bean的詳細配置 --> </bean> <bean id="..." class="..."> <!-- bean的詳細配置 --> </bean> <!-- 其他bean --> </beans>

id屬性是個字串,是bean的唯一標示符。class屬性定義了bean的類型,要使用類的全限定類名(含有包路徑)。id屬性的值,可以作為合作bean的引用標示符。上面未展示如何引用其他對象;詳情參看Dependencies

容器實例化

Spring IoC的實例化易如反掌。`ApplicationContext`構造函數支持定位路徑,定位路徑也可以是多個,它是標識實際資源的字串,容器使用該標識加載配置元數據,支持多種資源,比如:本地文件系統、CLASSPATH等等。 ```java ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); ``` ![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png) 在學習了Spring IoC容器之后,也許你想了解更多的Spring的資源,如前所述在第6章,資源使用URI語法定位輸入流,Spring提供了方便的機制讀取輸入流。在第6.7章[“Application contexts and Resource paths”](#resources-app-ctx),專門講述5用 資源路徑構造應用上下文,資源路徑也是慣用手法。 接下來的樣例展示了配置service層對象: ```xml

<!-- services -->

<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- 有關屬性配置 -->
</bean>

<!--更多的Service bean -->

```

下面的樣例展示了數據訪問對象dao.xml配置:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for data access objects go here --> </beans>

上述內容中service層由PetStoreServiceImpl類、2個dao對象JpaAccountDaoJpaItemDao(基於JPA ORM標准)。屬性name元素引用了JavaBean的屬性,ref元素引用了其他bean定義。這個引用表示實際對象之間的引用依賴。配置一個對象的依賴,詳情請參看Dependencies

引入基於xml的元數據

多個配置文件共同定義bean非常有用。通常,每個XML配置文件在你的架構中代表一個邏輯層或者一個模塊。

你可以使用應用上下文(applicationContext)的構造函數去加載所有xml中定義的bean。這個構造函數使用多個資源定位,就像前面中提到的。或者,也可以用一個或者多個資源引用,即使用<import/>標簽加載其他文件定義的bean。舉個栗子:

	<beans>
		<import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>

上例中,從三個外部文件加載定義的bean:services.xml,messageSource.xml,themeSource.xml 。被引入的文件的路徑對於引入配置文件來說都是相對路徑,所以service.xml必須在引入配置文件的相同文件路徑或者相同的類路徑中。而messageSource.xmlthemeSource.xml必須在引入配置文件所在的文件夾下的resouce文件夾下。正如你所看到的 /開頭會被忽略掉,因為這些路徑是相對路徑,推薦不要使用/開頭的格式。導入(imported)文件內容,包含根節點<beans/>,配置中XML bean定義 必須經過Spring語法校驗通過。

注意

使用"../"表示父目錄的相對路徑是可以的,但是真心不推薦這樣創建一個依賴應用外部文件的做法。尤其指出,使用*"classpath:"*資源類型的URLs(像這樣:"classpath:../services.xml"),也是不推薦的,因為運行時處理過程會選擇"最近的"根路徑然后引入他的父目錄配置文件。Classpath配置的改變,會導致應用選擇一個不同的、錯誤的目錄。 你可以使用全路徑限定資源定位取代相對路徑,比如:"file:C:/config/services.xml" 或者"classpath:/config/services.xml"。還有,你可以使用抽象路徑來解耦應用和配置文件。使用一個邏輯定位更可取 ,比如:通過"${..}"占位符,使用JVM運行時計算出的路徑。

使用容器

`ApplicationContext`是一個高級工廠的接口,能維護各種bean以及他們之間依賴的注冊。使用方法`T getBean(String name, Class requiredType)`,就能從定義的bean中獲取實例。 `ApplicationContext`能讓你讀取bean定義、訪問他們,如下: ```java // create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// retrieve configured instance PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance List userList = service.getUsernameList();


使用`getBean()`從beans中獲取實例。`ApplicationContext`接口有幾種方法可以辦到,但是理想的做法是不要使用他們。實際上,應用中根本就不該使用`getBean()`方法,這樣就不依賴Sprig API了。比如,Spring集成了很多web框架,為各種web框架類提供了依賴注入,比如web框架的Controller和JSF-managed beans

<h3 id='beans-definition'>Bean概述</h3>  
Spring IoC容器管理一個或多個bean。這些bean根據提供給容器的配置元數據創建的,比如使用XML格式`<bean/>`定義。  
在容器內部,這些bean的定義用 `BeanDefinition`對象表示,`BeanDefinition`包含了下列元數據:  
* 全路徑(含包路徑)類名:代表bean的實際實現類。
* Bean行為配置元素,它規定了bean在容器中行為(作用域,生命周期回調函數等等)
* 引用其他bean,就是為了bean能正常工作而所需的其他bean的引用。這些引用類也稱為合作類或者依賴類。
* 其他配置,為實例設置的其他屬性配置。比如說,管理連接池的bean的連接數,池大小的上限。

這些元數據將轉換成bean定義(`BeanDefinition`類)的屬性。

**The bean definition**  

**屬性**  | **詳情**  
------------- | -------------
**class** | [Section 5.3.2, “Instantiating beans”](#beans-factory-class)  
**name**  | [Section 5.3.1, “Naming beans”](#beans-beanname)  
**scope** | [Section 5.5, “Bean scopes”](#beans-factory-scopes)  
**constructor arguments** | [Section 5.4.1, “Dependency injection”](#beans-factory-collaborators)  
**properties** | [Section 5.4.1, “Dependency injection”](#beans-factory-collaborators)  
**autowiring mode** | [Section 5.4.5, “Autowiring collaborators”](#beans-factory-autowire)  
**lazy-initialization mode** | [Section 5.4.4, “Lazy-initialized beans”](#beans-factory-lazy-init)  
**initialization method** | [the section called “Initialization callbacks”](#beans-factory-lifecycle-initializingbean)  
**destruction method** | [the section called “Destruction callbacks”](#beans-factory-lifecycle-disposablebean)  

除了bean的信息以外,`BeanDefinition`也包含創建特殊bean的信息,`ApplicationContext`的實現也允許注冊由用戶創建而非IoC容器創建的對象。通過訪問ApplicationContext’s BeanFactory的方法`getBeanFactory()`,該方法返回BeanFactory的實現`DefaultListableBeanFactory`。`DefaultListableBeanFactory`類支持這種注冊,通過`registerSingleton(..)`和`registerBeanDefinition(..)`方法實現。然而,典型的應用只用元數據定義的bean就可以單獨運行。


<h4 id='beans-beanname'>beans命名</h4>
bean有一個或者多個標示符。這些標示符必須是所在容器范圍內必唯一的。通常情況一下,一個bean僅有一個標示符,如果有需求需要多個,多出來的將被當做別名。

在XML格式配置元數據中,使用 `id` 或者 `name` 屬性來作為bean的標示符。`id`屬性只能有1個。命名規范是字符數字混編(myBean,fooService,等等),但也支持特殊字符,可以包含。若想給bean起個別名,則可使用`name`屬性來指定,可以是多個,用英文的逗號(`,`)分隔、分號(`;`)也行、空格也行。注意,在Spring3.1以前,`id`屬性定義成了`xsd:ID`類型,該類型強制為字符*(譯者心里說:估計字母+特殊字符,不支持數字的意思,有待驗證,沒工夫驗證去了,翻譯進度太慢了。再說了,現在都用4了,你再說3.0怎么着怎么着,那不跟孔乙己似的跟別人吹噓茴香豆有四種寫法)*。3.1版開始,它被定義為`xsd:string`類型。注意,bean `id`的唯一性約束依然被容器強制使用,盡管xml解析器不再支持了。*譯者注:在spring3(含)以前,id是可以相同的,容器會替換相同id的bean,而在新版中,容器初始化過程中發現id相同拋出異常,停止實例化*

`id` 和`name`屬性不是bean所必須的。若未明確指定`id`或者`name`屬性,容器會給它生成一個唯一name屬性。當然了,如果你想通過bean的`name`屬性引用,使用`ref`元素方式,或者是類似於[Service Locator模式](#beans-servicelocator)方式檢索bean(*譯者想:應該是指調用ApplicationContext.getBean()方法獲取bean,類似這種方式。Service Locator是一種設計模式,其實換個名字是不是更合適,DL(Dependency Lookup依賴查找)。雖然現在我也不明白,但是下面有專門的章節講解,翻到時候再詳細了解*),就必須給bean指定	`name`了。之所以支持無name bean特性,是為了使內部類自動裝配。

Bean命名規范

bean命名規范使用java命名規范中實例屬性名(也稱域,Field)規范。小寫字母開頭的駝峰式。像這樣 (不包括單引號)accountManageraccountServiceuserDao, loginController,等等

規范的命名使配置易讀易理解。若使用Spring AOP,通過名字增強(譯注:大多數Spring AOP教材中 的 通知)一坨bean時,規范的命名將帶來極大的方便。

<h5 id='beans-beanname-alias'>bean定義之外設置別名</h5>
定義的bean內,可以給bean多個標識符,組合`id`屬性值和任意數量的`name`屬性值。這些標識符均可作為該bean的別名,對於有些場景中,別名機制非常有用,比如應用中組件對自身的引用。(*譯注:一個類持有一個本類的實例作為屬性,看起來應該是這樣的,以下代碼為推測,可以執行*)  
**Bean類**

```java	
public class SomeBean {
	//注意看這個屬性,就是本類
	private SomeBean someBean;
	
	public SomeBean(){}
	
	public void setSomeBean(SomeBean someBean) {
		this.someBean = someBean;
	}
}

配置元數據

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--看bean的別名,使用,/;/空格 分隔都是可以是--> <bean id="someBeanId" name="someBean,someBeanA;someBeanB someBeanC" class="com.example.spring.bean.SomeBean"> <!--將別名為someBeanA 的bean 注入給 id為someBeanId 的bean的屬性 'someBean'--> <property name="someBean" ref="someBeanA"></property> </bean> </beans>

測試代碼

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class) public class SomeBeanTests { @Autowired @Qualifier("someBeanId") private SomeBean someBean; @Test public void testSimpleProperties() throws Exception { } }

在bean的定義處指定所有別名有時候並不合適,然而,在其他配置文件中給bean設置別名卻更為恰當。此法通常應用在大型系統的場景中,配置文件分散在各個子系統中,每個子系統都有本系統的bean定義。XML格式配置元數據,提供<alias/>元素,可以搞定此用法。

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

這種情況下,在同容器中有個叫fromName的bean,或者叫其他的阿貓阿狗之類的,再使用此別名定義之后,即可被當做toName來引用。

舉個栗子,子系統A中的配置元數據也許引用了一個被命名為subsystemA-dataSource的bean。子系統B也許引用了一個subsystemB-dataSource。將這兩個子系統整合到主應用中,而主應用使用了一個myApp-dataSource,為了使3個bean引用同一個對象,得在MyApp配置元數據中使用別名定義:

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

現在,每個組件和主應用都能通過bean 名引用dataSource,而bean名都是唯一的保證不與其他定義沖突(實際上創建了一個命名空間),但他們引用的都是同一個bean。

Java-configuration

如果你使用了Java-configuration@Bean注解也提供了別名,詳見Section 5.12.3, “Using the @Bean annotation”

bean實例化

bean的定義,本質是如何創建一個或多個對象的配方。容器被請求時,會解析配置元數據中的bean定義並封裝,使用封裝配置創建(或者獲取)對象實例。

若使用XML格式配置元數據,得為將要實例化的對象指定類型(或者說是類),使用<bean/>元素的class屬性實現。class屬性 映射到BeanDefinition類實例的Class屬性(域),這個class屬性是<bean/>元素必須的。(例外情況,參看“Instantiation using an instance factory method” 和 Section 5.7, “Bean definition inheritance”。使用Class域屬性,通過以下兩種方式:

  • 通常,通過指定bean的class 屬性,容器使用反射調用其構造函數直接創建bean,有點像Java 編碼中使用new操作符。
  • 指定class實際類含有用於創建對象的靜態工廠方法,這是不常使用的場景,容器會調用類的靜態工廠方法創建bean。調用靜態工廠方法返回的對象類型也許是相同類型,也許完全是其他類。
內部類命名 若要定義靜態內部類,得將類名劈開。

舉例來說,現在在com.example包有個類Foo,該類有靜態內部類Bar,定義Bar的Spring bean的`class`屬性差不多是這樣

com.example.Foo$Bar 

注意$字符,用它來分隔內部類名和外圍類名

用構造函數實例化

若是使用構造函數方式創建bean,所有的常規類都可以使用Spring來創建、管理。也就是說,開發的類無需實現任何特殊接口或者使用某種特殊編碼風格。僅需指定bean的`class`即可。對於特殊的bean管理,取決於你使用的IoC類型,也許需要一個默認的空構造。

Spring IoC容器幾乎能管理任何你需要管理的類,不局限於真正的JavaBeans。大多數Spring的用戶心中,真正的JavaBean是這樣的:僅有1個默認的無參構造函數、屬性、setter、getter。嗯,比如,現在需要使用一個廢棄連接池,它肯定不符合JavaBean規范,Spring照樣能管理。

使用XML格式配置元數據 定義bean的class,如下所示:

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

如何為構造函數指定參數?如何在對象實力話之后設置其屬性?請參看Injecting Dependencies

使用靜態工廠方法實例化

定義使用使用靜態工廠方法創建的bean時,得指定工廠方法類的作為`class`屬性值,並且還得指定工廠方法類中用於創建bean的方法名稱,作為`factory-method`屬性值。工廠方法可以有參數,調用該方法即可返回對象實例,就像通過構造函數創建對象實例一樣。此種bean定義是為了兼容遺留系統中的靜態工廠

下面的bean定義,是使用工廠方法創建bean的方式。定義中,無需指定返回對象的類型(class),而是指定工廠方法類的class。下例中,createInstance()方法必須是一個static靜態方法。

<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; } } 
使用實例工廠方法實例化

和[靜態工廠方法](#beans-factory-class-static-factory-method)類似的還有實例工廠方法,使用實例工廠方法的方式實例化,是調用容器中已存在的bean的一個非靜態方法來創建一個bean。用法是,1、`class`屬性置空設置。 2、設置`factory-bean`屬性,其值為當前容器(或者父容器)中bean的名字,該bean包含可供調用的創建對象的實例方法。3、設置`factory-method`屬性,其值為工廠方法名。 ```xml

 


工廠類如下
```java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}
    
    public ClientService createClientServiceInstance() {
    return clientService;
    }
}

工廠類可以有多個工廠方法:

<bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> <bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>

工廠類如下:

public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } }

上例中展示了工廠類本身也可以通過 DI 管理和配置。參看DI詳情

注意

Srping 資料中,factory bean是指一個Spring配置的bean,該bean能通過實例或者靜態工廠方法創建對象。對比之下,FactoryBean(注意大寫)是指Spring術語FactoryBean。這段沒太理解,解釋factory bean和FactoryBean

依賴

企業應用絕不會只有1個簡單對象(或者說Spring bean)。哪怕是最簡單的應用,也會包含許多對象協同工作。下一章節講述,如何為真正的應用定義大量的、獨立的bean,並讓這些對象一起合作。

依賴注入

*依賴注入(DI)*,是一個有對象定義依賴的手法,也就是,如何與其他對象合作,通過構造參數、工廠方法參數、或是在對象實例化之后設置對象屬性,實例化既可以構造也可以是使用工廠方法。容器在它創建bean之后注入依賴。這個過程從根本上發生了反轉,因此又名控制反轉(Ioc),因為Spring bean自己控制依賴類的實例化或者定位 ,Spring bean中就有依賴類的定義,容器使用依賴類構造器創建依賴類實例,使用*Service Locator*模式定位依賴類。

DI機制使代碼簡潔,對象提供它們的依賴,解耦更高效。對象無需自己查找依賴。同樣的,類更容易測試,尤其當依賴接口或者抽象類時,測試允許在單元測試中使用stub或者mock(模擬技術)實現。

DI有2種主要方式,構造注入 和 setter注入
構造注入,容器調用構造函數並傳參數,每個參數都是依賴。調用靜態工廠方法並傳參數方式構造bean和構造注入差不多,這里是指構造注入處理參數和靜態工廠方法處理參數像類似。下例中展示了一個只能使用構造注入的類。注意,此類無任何特別之處,並未依賴容器指定的接口、基類、注解,就是一個POJO

public class SimpleMovieLister { // the SimpleMovieLister 依賴 a MovieFinder private MovieFinder movieFinder; //Spring容器能注入MovieFinder的構造函數 public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // 實際如何使用MovieFinder的業務邏輯省略了 }
構造函數參數解決方案

構造參數解決方案,會匹配所使用的參數類型。如果在bean的定義中,構造參數不存在歧義,那么,在bean定義中定義的構造參數的次序,在bean實例化時,就是提供給適合的構造參數的次序。看這個類: ```java package x.y;

public class Foo {

public Foo(Bar bar, Baz baz) {
    // ...
}

}


不存在歧義,假設`Bar`和`Baz`類沒有集成關系,那么下面的配置是合法的,而且,不需要在`<constructor-arg/>`元素里指定構造參數的明確的`indexes`索引或者類型。
```xml
<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>

若需要引用另一個bean,類型已知,構造函數就可以匹配參數類型(像上面的示例)。使用簡單類型時, 想<value>true</true>,Srping不能決定value類型情況,Spring就不能自己匹配類型。例如:

package examples; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }

上面的場景中,如果使用type屬性明確指定構造參數的類型,容器就可以使用類型匹配。比如:

	<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>

使用index屬性明確指定構造參數的次序。比如

	<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>

當構造函數有2個相同類型的參數,指定次序可以解決此種情況。注意index是從0開始

	<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>

記住,若要使Spring能從構造函數查找參數名字,代碼在編譯時必須開啟調試模式。若你沒有開啟調試模式(或者不想),可以使用@ConstructorProperties JDK 注解明確指定構造參數的name。樣例程序:

package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
setter注入

Setter注入是容器調用bean上的setter方法,bean是使用無參構造函數返回的實例,或者無參靜態工廠方法返回的實例。 下面樣例中展示了只能使用Setter注入的類。這個類是傳統java類,就是個POJO,不依賴容器指定的接口、基類、注解。 ```java 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支持構造注入和setter注入。也支持先構造注入再setter注入。定義依賴,會轉換成某種形式的<code class="scode">BeanDefinition</code>類,<code class="scode">BeanDefinition</code>類與<code class="scode">PropertyEditor</code>實例配合,即可將屬性從一種格式轉換成其他格式。然而,大多數程序員不會直接使用這些類(也就是編程式),更多的是使用XML、注解(也就是<code class="scode">@Component</code><code class="scode">@Controller</code>等等),或者<code class="scode">@Configuration</code>注解的類中的方法上使用 <code class="scode">@Bean</code>。這些配置數據,都會在容器內部轉換成`BeanDefinition`,用於加載整個Spring Ioc 容器。

**構造注入對比setter注入**

何時使用構造注入,何時使用setter注入,經驗法則是:強制依賴用構造,可選依賴用Setter。注意,在settter方法上使用<code class="scode">[@Required](#beans-required-annotation)</code>注解即可另屬性強制依賴。

Spring 團隊建議,構造注入的實例是不可變的,不為null的。此外,構造注入組件要將完全初始化后的實例返回給客戶端代碼。還有,大量參數的構造函數是非常爛的,它意味着該類有大量的職責,得重構。

setter注入主要用於可選依賴,類內部可以指定默認依賴。否則類內所有使用依賴的地方,都得進行非空校驗。setter注入的有個好處就是,類可以重配置或者再注入。因此,使用`JMX MBeans`進行管理的場景中,就非常適合setter注入。

使用何種依賴注入方式,對於某些類,非常有意義。有時協同第三方類處理,沒有源碼,由你來決定使用何種方式。比如,第三方類未暴露任何setter方法,那么構造注入也許就是唯一的可行的注入方式了。


<h5 id="beans-dependency-resolution">依賴處理過程</h5>
容器解析bean依賴如下:
* `ApplicationContext`創建后用配置元數據中描述的所有bean進行初始化。配置元數據格式可以是XML、Java Code,或者注解。
* 每個bean的依賴,都會以下列形式表達:屬性、構造參數,靜態工廠方法的參數。當bean真正的創建時,這些依賴會被提供給bean。
* 每個屬性或者構造函數或者以value值形式在bean處直接設置,或者引用容器中其他bean。
* 每一個屬性或者構造參數都是一個值,該值將會從指定的格式轉換為屬性、構造參數的真正類型。Spring默認會將一個`String`類value轉換成內建類型,比如`int`,`long`,`String`,`boolean`等等 

Spring容器在創建bean之前會驗證bean的配置。在bean創建之前,bean的屬性不會賦值。當容器創建之后,會創建被設置為預先初始化的`sington-scope`單例作用域bean,非單例作用域bean,只有在請求時才會創建。作用域,在5.5章有定義,["Bean 作用域"](#beans-factory-scopes)。一個bean的創建,可能會引起許多bean的創建。因為bean的依賴以及依賴的依賴得先創建好用於引用。不涉及首先創建的bean及其依賴類bean,會稍后創建。

**循環依賴**
如果你主要使用構造注入,可能會創建一個循環依賴,該依賴不能解析。  

舉個栗子:類A需要類B的實例,使用了構造注入,類B需要一個類A的實例,也用了構造注入。若在配置文件中配置類A的bean和類B的bean互相注入,Spring IoC容器在運行時發現循環引用,拋出異常`BeanCurrentlyInCreationException`。  

一般使用Setter注入替代構造注入,這需要修改源碼改配置,來解決循環依賴。避免使用構造注入或者只使用setter,都能避免循環依賴。 換句話說,雖然不推薦循環依賴,但是你可以使用setter注入來完成循環依賴。

和大多數場景(無循環引用)不一樣的是,循環引用中的類A和類B中,得強制其中一個自己能完全初始化,然后注入給另一個(經典的先有雞現有蛋的問題)。
**循環依賴end**

對於Spring,你經管放心,它非常智能。他能在容器加載期發現配置中的問題,比如:引用了一個不存在的bean、循環依賴。Spring在bean創建后,會盡可能遲的設置bean屬性並處理依賴。這意味着,spring容器正確加載之后,當你請求一個對象而該對象的創建有問題或者是該對象的依賴有問題時,也能產生一個異常。舉例來說,因為屬性找不到,或者屬性無效, 導致bean拋出異常。這可能會延遲發現配置問題,這就是為什么`ApplicationContext`默認會預先實例化單例bean。在這些bean被實際請求之前就創建,會消耗一些時間和內存,但是在`ApplicationContext`創建后你就能發現配置問題,而不是更遲。如果你願意 ,也可以重寫該行為,讓單例bean延遲初始化。


如果沒有循環依賴,當一個或者多個合作bean被注入到他們的依賴類時,每一個合作bean將會比依賴類更早的實例化。也就是說,如果bean A依賴bean B,Spring Ioc容器在調用A的setter方法之前,會先實例化B。換句話說,bean先實例化(非單例),然后設置依賴,然后調用相關聲明周期方法(比如配置的init方法,或者是初始化回調函數)。

<h5 id='beans-some-examples'>注入依賴樣例</h5>
The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions:
下面例子中使用了XML配置元數據,setter注入方式。XML 配置文件中的片段定義了bean:
```xml
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- 使用內嵌的ref元素完成setter注入 -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- 使用ref屬性完成setter注入 -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

看java代碼

public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }

In the preceding example, setters are declared to match against the properties specified in the XML file. The following example uses constructor-based DI: 上例中,setter方法名要和XML文件中的property元素的name屬性相匹配。下面演示使用構造注入 :

<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"/>

看java代碼

public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } }

在bean定義中指定的構造函數參數,將會賦值給ExampleBean類的參數。

現在考慮下這個樣例的變種,將使用構造器改為靜態工廠方法返回對象實例:

<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"/>

看java代碼

public class ExampleBean { //私有構造函數 private ExampleBean(...) { ... } // 靜態工廠方法; 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; } }

靜態工廠方法的參數,應該通過constructor-arg元素產生,就像是bean的構造函數一樣.工廠方法返回的類的類型無需和工廠類類型相同,雖然本例中他們是相同的。實例工廠方法(非靜態)和靜態工廠方法本質相同(除了使用facory-bean屬性替代class屬性,其他都相同),因此細節就不討論了。

依賴和配置詳解

前面章節提到的,你可以定義的bean的屬性和構造參數引用其他的Spring bean(合作者),或者是使用value屬性設置其值。Spring XML格式配置元數據至此``和``子元素,用以實現構造注入和屬性注入。

直接賦值(原始類型、String等等)

``元素的`value`屬性為對象域屬性或者構造參數設置了一個可讀的字串。Spring的會將其轉換為實際的與屬性或者參數的數據類型。 ```xml ```

下面的樣例,是使用了XML配置中的p命名空間,他讓XML更加簡潔

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli"/> </beans>

上面的XML更簡潔;然而,錯別字,要在運行期才能發現而不能再開發期發現,除非你使用IDE支持自動補全。這樣的的IDE的助手真心推薦。

也可以這樣配java.unit.Properties實例:

<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- typed as a java.util.Properties --> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property> </bean>

Spring 容器通過JavaBean的PropertyEditor機制將<value/>元素內的值轉換到java.util.Properties實例。這是非常棒的,Spring團隊最喜歡的幾處好用之處之一:用內嵌<value/>元素替代 值屬性風格。

元素idref

`idref`元素用來將容器內其它bean的id傳給`` 或 ``元素,同時提供錯誤驗證功能。 ```xml ``` 上面的bean定義在運行時等同於下面這一段定義:

<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean" /> </bean>

第一種格式比第二種要跟可取 ,因為使用idref標簽,在開發期將允許容器校驗引用bean真是存在。在第二個中,對於client bean是屬性 targetName的值則沒有校驗執行 .client bean真正的實例化時,錯別字才會被發現(可能會導致致命錯)。如果client bean是一個原型bean,這個錯字導致的異常也許會等到部署后才能被發現。

注意

在4.0 beans xsd ,idref上的local屬性不在支持。因此它能提供正規bean引用 。當你升級到4.0的語法時,記得清除已經存在於idref元素上的local屬性。

一個老生常談的問題(至少是2.0以前了),<idref/>帶來的好處,在使用ProxyFactorybeanbean定義AOP攔截器時,當指定攔截器名字是使用<idref/>元素將,容器會校驗攔截目標是否存在。

引用其他bean(協作類)

`ref`元素是``元素和``元素內決定性元素。用它設置bean的屬性以引用另一個容器管理的bean。引用的bean就是要設置屬性的bean的依賴,在設置屬性值之前它就要被初始化。(如果協作類是單例bean,它會在容器初始化時首先完成初始化)。差不多所有的bean都會引用其他對象。指定`id/name`的對象的作用域和依賴校驗通過`bean`,`local` ,`parent`屬性來配置。 指定引用bean通常使用``標簽,它允許引用本容器或者父容器中任意的bean,無需配置在同一個xml文件中 。``標簽中`bean`的屬性值,使用的被引用bean的`id`或者`name`。 ```xml ``` 通過指定目標bean的`parent`屬性來引用當前容器的父容器中的bean。`parent`屬性的值可以和引用bean的`id`或者`name`(引用bean的name之一)相同,引用的bean必須存在於當前容器的父容器中。若容器存在繼承的情況,並且需要封裝現有父容器中的某個bean到一個代理中,就可以用此種引用機制,一個與`parent` bean重名的bean。 ```xml ``` 子容器中 ```xml class="org.springframework.aop.framework.ProxyFactoryBean"> ``` ![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png) > 在4.0 beans xsd ,`ref `上的`local`屬性不在支持。因次它不再支持正規bean的引用 。當你升級到到4.0時,記得清除已經存在於`ref`元素上的`local`屬性。

內部bean

在``元素或者`constructor-arg/>`元素內定義``元素,就是所謂的內部類。 ```xml ```

內部bean的定義無需idname;容器會忽略這些屬性。也會忽略scope標記。內部通常是匿名的,伴隨着外部類(的創建)而創建 。不能引用內部bean(ref屬性不能指向內部bean),除非使用閉合bean標簽。

譯者注,內部bean更直觀 fuck goods,上干活

public class Customer { private Person person; public Customer(Person person) { this.person = person; } public void setPerson(Person person) { this.person = person; } @Override public String toString() { return "Customer [person=" + person + "]"; } }

再來一段

public class Person { private String name; private String address; private int age; //getter and setter methods @Override public String toString() { return "Person [address=" + address + ",  age=" + age + ", name=" + name + "]"; } }

通常情況下,使用在CustomerBeanbean內設置ref屬性值為Personbean的標示符,即完成注入。

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="CustomerBean" class="com.example.common.Customer"> <property name="person" ref="PersonBean" /> </bean> <bean id="PersonBean" class="com.example.common.Person"> <property name="name" value="MrChen" /> <property name="address" value="address1" /> <property name="age" value="28" /> </bean> </beans>

In general, it’s fine to reference like this, but since the ‘MrChen’ person bean is only used for Customer bean only, it’s better to declare this ‘MrChen’ person as an inner bean as following : 一般情況下,這樣的引用很好用。但是如果'MrChen'這個person bean只用於Customer。最好是使用內部bean來聲明Person,看起來更加直觀,更具有可讀性.

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="CustomerBean" class="com.mkyong.common.Customer"> <property name="person"> <bean class="com.mkyong.common.Person"> <property name="name" value="mkyong" /> <property name="address" value="address1" /> <property name="age" value="28" /> </bean> </property> </bean> </beans>

This inner bean also supported in constructor injection as following : 內部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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="CustomerBean" class="com.mkyong.common.Customer"> <constructor-arg> <bean class="com.mkyong.common.Person"> <property name="name" value="mkyong" /> <property name="address" value="address1" /> <property name="age" value="28" /> </bean> </constructor-arg> </bean> </beans>
集合

``,``,``,``元素,用來設置`Java Collection`屬性和參數,分別對應`List`,`Set`,`Map`,`Properties` ```xml administrator@example.org support@example.org development@example.org a list element followed by a reference just some string ```

map.key,map.value,或者set.value,以可以是以下元素

bean | ref | idref | list | set | map | props | value | null
集合合並

集合合並 Spring容器也支持集合合並。應用開發者可以定義父集合``,``,``或者``元素,該元素可以有子集合``,``,``或者``元素集成或者重寫父集合中的值。也就是,子集合中的值是合並父子集合后的值,其中子集合中的值會覆蓋父集合中的值。 *這一章節討論父-子bean機制。不熟悉父子bean定義機制的,最好是先去補充下然后回來繼續* 下例中展示了集合合並

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

注意,在bean child定義中,指定property adminEmails<props/>元素中merge=true屬性。當childbean被容器解析並且實例化時,實例有一個adminEmailsProperties集合,該集合包含了父子容器中adminEmails集合合並后的值。

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

Properties集合的將繼承所有父集合中<props/>定義的值,並且重名屬性值會覆蓋父集合中的值.

這個合並行為,同樣可以用在<list/>,<map/>,<set/>類型上。對於<list/>元素,spring合並行為與List類型的合並一樣,也就是,spring合並行為維持集合有序;父集合中的元素索引位置比子集合元素索引位置靠前。對於Map,Set,Properties集合類型,不存在次序。因此,沒有次序影響Map,Set,Properties,這涉及到容器內部使用的這些類型的所有實現類。 譯注:這里沒有提到List會不會發生覆蓋 ,既然沒提到,那就是List沒有覆蓋行為。當然了,實踐才是王道,動手實驗才能驗證推測,研讀源碼才能知道原理,下面上干貨

Java代碼

public class CollectionMerge { private List<String> list; public List<String> getList() { return list; } public void setList(List<String> list) { this.list = list; } }

XML配置

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="parent" class="com.example.spring.bean.collection.CollectionMerge"> <property name="list"> <list > <value>1</value> <value>2</value> </list> </property> </bean> <bean id="child" parent="parent" > <property name="list"> <list merge="true"> <value>1</value> <value>2</value> </list> </property> </bean> </beans>

測試代碼

import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class MapMergeTests { @Autowired @Qualifier("child") private CollectionMerge service; @Test public void testSimpleProperties() throws Exception { assertEquals(4, service.getList().size()); } } 

經過動手實驗,證明Spring合並對於List類型,並無覆蓋。接下來,我們看看其源碼實現

MutablePropertyValues.mergeIfRequired()方法

private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) { Object value = newPv.getValue(); //屬性值是否可合並類 if (value instanceof Mergeable) { Mergeable mergeable = (Mergeable) value; //屬性值是否設置了merge=true if (mergeable.isMergeEnabled()) { //合並當前屬性 Object merged = mergeable.merge(currentPv.getValue()); return new PropertyValue(newPv.getName(), merged); } } return newPv; }

上面代碼中Mergeable接口共有5個實現類ManagedList,ManagedArray,ManagedMap,ManagedSet,ManagedProperties

ManagedList.merge(Object parent)方法

public List<E> merge(Object parent) { //防御性拋異常 if (!this.mergeEnabled) { throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); } if (parent == null) { return this; } //不能合並非List類型集合 if (!(parent instanceof List)) { throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); } List<E> merged = new ManagedList<E>(); //注意順序,先增加父bean中的value值,所以文檔中說父集合元素索引位置靠前 merged.addAll((List) parent); merged.addAll(this); return merged; }

譯注:我勒個去,為了找這段代碼,灑家差點累吐血。由此可見,譯者是非常用心的用生命去翻譯文檔。

合並限制 不能合並不同類型的集合,比如合並MapList(譯注:上面的源碼中有這樣的代碼,不知聰明的小讀者是否注意到了)。如果你非得這么干,那么就會拋出個異常。merge屬性必須指定給父-子繼承結構bean中的子bean,如果指定給了父集合則無效,不會產生預期的合並結果。

強類型集合 Java5中出現了范型,所以可以給集合使用強類型限制。比如說,聲明一個只含有String類型的Collection。若使用Spring 注入一個強類型Collection給一個bean,那么就可以利用Spring的類型轉換特性 ,該特性能將給定的值轉換成合適的類型值,然后賦值給你的強類型Collection

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>

foobean的accounts屬性注入前,強類型集合Map<String,Float>的泛型信息通過反射獲取。因此Spring的類型轉換機制識別出元素的value類型將會轉換為Float9.99,2.75,3.99將會轉換成Float類型。

Null值和空字串

Spring treats empty arguments for properties and the like as empty Strings. The following XML-based configuration metadata snippet sets the email property to the empty String value (""). Spring對於屬性的空參數轉換為空字串。下面的XML片段,設置值email屬性為空格字串("")

<bean class="ExampleBean"> <property name="email" value=""/> </bean>

上面的xml配置相當於這樣的java代碼

exampleBean.setEmail("")

<null/>元素處理null值:

<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>

上面配置相當於

exampleBean.setEmail(null)
XML簡寫p命名空間

p-命名空間能讓你使用`bean`元素屬性替代內嵌`property/>`元素,用來描述屬性值或者協作類。

Spirng支持命名空間擴展配置,命名空間基於XML Schema定義。本章討論的beans配置格式定義在XML Schema文檔中。然而,p命名空間並不是在XSD文件中,而是存在於Spring核心中。

下面XML片段解釋了:1使用了標准XML,第2個使用p-命名空間

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="foo@bar.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/> </beans>

上例中解釋了,在bean定義中使用p-namespace設置email 屬性。它告訴Spring這里有一個property聲明。前面提到過,p-namespace 並不存在schema定義,所以p可以修改為其他名字。 譯注,干活都是譯者自己撰寫用於驗證,而非參考手冊原版中的內容,之所以驗證,是因為原版E文有看不懂的地方、或者翻譯拿不准、或者就是閑來無事、或者就是為了湊篇幅,這些事兒得通過寫代碼驗證搞定了 up fuck goods上干貨

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ppp="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="p-namespace" class="com.example.spring.bean.p.PNamespaceBean" ppp:email="foo@email.com"/> </beans>

注意p命名空間的用法xmlns:ppp="http://www.springframework.org/schema/p"ppp:email="foo@email.com"

go on fuck goods繼續干貨

public class PNamespaceBean { private String email; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }

下面樣例中,2個bean都引用了同一個bean

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!--傳統-->
    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

	<!--時髦-->
    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

	<!---->
    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

樣例中2個p-namespace設置屬性值,出現了一種新的格式聲明引用。第一個bean中,使用了 <property name="spouse" ref="jane"/>應用bean jane.第二個bean中,使用了 p:spouse-ref="jane"做了相同的好事兒,此時,spouse是屬性名,然而-ref表示這不是直接量而是引用另一個bean。

譯注 好事兒,我小時候雖然做好事兒不留名,但是總能被發現,令我非常苦惱。我的媽媽常常揪着我的耳朵問:這又是你干的好事兒吧。

c-namespace命名空間

p-namespace相似,c-namespace,是Spring3.1中新出的,允許行內配置構造參數,而不需使用內嵌的constructor-arg元素

c:namespace重構構造注入

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- traditional declaration --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> </beans>

c:namespace和p:使用了相同機制(ref后綴表示引用),通過names設置構造參數。因為它未定義在XSD schema中(但是存在於Spring內核中),所以需要先聲明。

有一些情況比較特殊,不能識別或者看到構造參數(比如無源碼且編譯時無調試信息),此時可以求助於參數索引:

<!-- c-namespace index declaration --> <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

注意

由於XML語法,索引標記需要_開頭作為XML屬性名稱,而不能使用數字開頭(盡管某些ID支持)

在實際中,構造注入(name匹配/類型匹配/索引匹配)是非常高效的,一般情況下,推薦使用 name匹配方式配置。

復合屬性

在設置bean屬性時,可以使用復合或者內嵌屬性,組件路徑可以有多長寫多長,除了最后一個屬性,其他屬性都不能為`null`。看下面的bean定義 ```xml ``` bean `foo`有屬性`fred`,`fred`有屬性`bob`,`bob`有屬性`sammy`,最后的`sammy`屬性賦值`"123"`。在bean`foo`構造后,`fred`屬性和`bob`屬性都不能為`null`否則拋異常`NullPointerException`

使用depends-on

若bean是另個bean的依賴,通常是指該bean是另個bean的屬性。在XML中通過<ref/>元素配置實現。然而,bean之間並不全是直接依賴。舉個栗子,類中有個靜態初始化需要出發,像注冊數據庫驅動這樣的。depends-on屬性能強制這些先決條件首先完成執行初始化,然后再去使用它(比如用於注入)。 下面的樣例中,展示了使用depends-on來表達bean之間的依賴關系:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />

也可以依賴多個bean,為depends-on屬性值提供一個bean name列表,用逗號,空白,分號分隔。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </bean> <bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

注意

單例bean中,depends-on屬性既可以設定依賴的初始化時機,也可以相應的設定依賴的銷毀時機。在bean被銷毀之前,bean使用depdnds-on屬性定義的依賴bean會首先被銷毀。因此depends-on也能控制銷毀順序。

延遲初始化

`ApplicationContext`的各種實現默認的初始化處理過程,都是盡早的創建、配置所有的單例bean。通常,這種預先實例化是非常好的,因為在配置的錯誤或者環境問題立刻就能暴露出來,而不是數小時甚至數天后才發現。若不需要此行為,可以通過設置`lazy-initialized`延遲加載來阻止預先初始化。`lazy-initialized`bean告訴Ioc容器,只有在第一次請求的時候采取初始化,而不是在啟動容器時初始化。

在XML中,屬性lazy-init控制<bean/>元素的初始化。

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.foo.AnotherBean"/>

ApplicationContext解析上面的配置,在啟動時,不會預先初始化這個標記為lazy的bean,為標記lazy的bean則會立刻初始化。

如果一個非延遲的單例bean依賴了lazy延遲bean,ApplicationContext會在啟動時就創建lazy延遲bean,因為它必須滿足單例bean依賴。延遲bean注入給單例bean,就意味着,它不會延遲加載的。

通過設置<beans/>元素的default-lazy-init屬性,可以設置容器級別的延遲加載。看樣例:

<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>

自動裝配

Spring容器提供自動裝配,用於組織bean之間的依賴。可以讓Spring,通過檢查`ApplicationContext`內容完成自動注入。自動裝配有以下優點:

  • 自動裝配能明顯減少屬性和構造參數的配置。(在這方面,還有其他的機制也能達到此目的,比如bean 模板,在后面的章節里有詳細的講解
  • 自動裝配可擴展性非常好。比如,給類增加了依賴,無需修改配置,依賴類就能自動注入。因此,自動裝配在開發期非常有用,在代碼不穩定時,無需修改編碼即可完成切換。

在XML中,設置<bean/>元素的autowire屬性來設定bean的自動裝配模式。自動裝配有5種模式。可以選擇任意一種,來設置bean的裝配模式。(譯注,這不是廢話么,有5中模式,每種都能隨便用,假設有一種不能用,那就是4種模式了么

Table 5.2. 自動裝配模式

模式 解釋
no (默認的)非自動裝配。必須使用ref元素定義bean引用。對於大規模系統,推薦使用,明確的指定依賴易於控制,清楚明了。它在某種程度上說明了系統的結構。
byName 通過屬性名稱property name自動裝配。Spring會查找與需要自動裝配的屬性同名bean。舉個栗子,若在bean定義中設置了by name方式的自動裝配,該bean有屬性master(當然了,還得有個setMaster(..)寫屬性方法),Spring會查找一個名叫master的bean,並將它注入給master熟悉。
byType 若在容器中存在一個bean,且bean類型與設置自動裝配bean的屬性相同,那么將bean注入給屬性。若與屬性同類型的bean多於1個,則會拋出我們期待已久的致命異常,也就意味着這個bean也許不適合自動注入。若不存在匹配的bean,啥都不會發生;屬性也不會設置,然后就沒有然后了。
constructor Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.和byType模式類似,但是應用於構造參數。若在容器中不存在與構造參數類型相同的bean,那么接下來呢,拋異常唄,還能干啥?

byType或者constructor自動裝配模式,可以裝配arrays數組和范型集合。 這種個情況 下,容器內匹配類型的bean才會注入給依賴。若key類型是String,你也能自動裝配強類型Maps。一個自動裝配的Map,所有 匹配value類型的bean都會注入Map.value,此時,Map的key就是相應的bean的name。

可以結合自動裝配和依賴檢查,依賴檢查會在裝配完成后執行。

自動裝配的局限和缺點

自動裝配最好是貫穿整個項目。若不是全部或大部分使用自動裝配,而僅僅自動裝配一兩個bean定義,可能會把開發者搞暈。(*譯注,最可能的原因是,開發者對自動裝配機制不熟悉,或者想不到項目中居然還使用了自動裝配模式,當發生問題時,擦的,找都沒地方找去,調試信息里只能看到經過橫切織入事務代理的proxy*)

總結局限和缺點:

  • 屬性中和構造參數明確的依賴設置會覆蓋自動裝配。不能自動裝配所謂的簡單屬性,比如原始類型,StringsClasses(簡單屬性數組)。這是源於Spring的設計。
  • 和明確裝配相比,自動裝配是不確切的。正如上面的列表中提到的,Spring謹慎避免匹配模糊,若真的匹配不正確,則導致錯誤發生,Spring 管理的對象之間的關系記錄也變的不明確了。
  • 對於根據Spring容器生成文檔的工具,裝配信息將變的無用。
  • 容器內多個bean定義可能會匹配設置為自動裝配的setter方法或者構造參數的類型。對於arrays,collections,或者maps,這不是個問題。然而對於期望單一值的依賴,這種歧義將不能隨意的解決。如果發現多個類型匹配,將會拋出異常 .

在后面的場景中,給你幾條建議:

  • 放棄自動裝配,使用明確裝配
  • 避免通過在bean定義中設置autowire-candidate屬性為false的方式來設置自動裝配,下一章節會講
  • 通過設置<bean/>袁術的primary屬性為true來指定單個bean定義作為主候選bean。
  • 使用基於注解的配置實現更細粒度的控制,參看Section 5.9, “Annotation-based container configuration”.
排除自動裝配bean

在每個bean的設置中,你可以排除bean用於自動裝配。XML配置中,設置``元素的`autowire-candidate`屬性為`false`;容器將不使用該bean自動裝配。(包括注解配置,像`@Autowired`) * 使用對bean名字進行模式匹配來對自動裝配進行限制。其做法是在元素的'default-autowire-candidates'屬性中進行設置。比如,將自動裝配限制在名字以'Repository'結尾的bean,那么可以設置為"*Repository“。對於多個匹配模式則可以使用逗號進行分隔。注意,如果在bean定義中的'autowire-candidate'屬性顯式的設置為'true' 或 'false',那么該容器在自動裝配的時候優先采用該屬性的設置,而模式匹配將不起作用。*譯注這一段翻譯是從網上copy過來的,我勒個擦,得趕緊睡覺去了*

這些設置非常有用。但是這些被排除出自動注入的bean是不會自動注入到其他bean,但是它本身是可以被自動注入的。

方法注入

一般情況,容器中的大部分的bean都是[單例的](#beans-factory-scopes-singleton)。當單例bean依賴另一個單例bean,或者一個非單例bean依賴另個非單例bean是,通常是將另一個bean定義成其他bean的屬性。當bean的生命周期不同時,那么問題來了。假設單例bean A依賴非單例bean(prototype) B,也許會在每個方法里都需要B。容器之創建了一個單例bean A,因此只有一次將B注入的機會。A調用B,需要很多B的實例 ,但是容器不會這么干。

解決辦法是放棄一些IoC控制反轉。令A實現接口ApplicationContextAware,此時A能夠感知容器,即獲取ApplicationContext ,每次當A調用B時,調用容器的getBean("B")方法用以創建B的實例。看樣例:

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

並不推薦上面的做法,因為業務代碼耦合了Spring 框架。方法注入,是SpringIoc容器的高級特性,能夠簡潔的滿足此場景。

想要了解更多的方法注入,參看此博客

查找式方法注入

查找式是指,容器為了覆蓋它所管理的bean的方法,在容器范圍內查找一個bean作為返回結果。通常是查找一個原型(prototype)bean,就像是上面章節中提到過的場景。Srping框架,使用`CGLIB`類庫生成動態子類的字節碼技術,覆蓋方法。

注意

為了能讓動態子類能運行,其父類不能是final類,被覆蓋的方法也不能是final。還有,你得自己測試父類是否含有abstract方法,如果有,需要你提供默認實現。最后,被方法注入的對象不能序列化。Spring 3.2以后,不需要CGLIB的類路徑了,因為CGLIB被打包入了org.springframework 包,和Spring-core 這個jar包在一起了。既是為了方便也是為了避免CGLIB包與應用中用到的CGLIB包沖突。

來看看前面提到的CommandManager類的代碼片段,Spring容器會動態的覆蓋createCommand()方法的實現。這樣CommandManager類就不會依賴任何Spring API了。下面是修改過后的

package fiona.apple; // 不再有 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? //okay....但是方法實現在哪里? protected abstract Command createCommand(); }

在含有被注入方法的類中(像CmmandManager類),被注入方法需要使用以下簽名

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

動態生成子類會實現抽象方法。若該方法不是抽象的,動態生成自來則會重寫在源類中的方法。配置如下:

<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="command" 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="command"/> </bean>

commandManager類調用createCommand方法時,動態代理類將會被識別為commandManager返回一個command bean的實例。將commandbean設置成prototype,一定要小心處理。若被設置成了singleton,每次調用將返回同一個commandbean。

注意

感興趣的小讀者也找找ServiceLocatorFactoryBean類(在org.springframework.beans.factor.config包)來玩玩。使用ServiceLocatorFactoryBean類的處理手法和另一個類ObjectFactoryCreatingFactoryBean類似,但是ServiceLocatorFactoryBean類與虛擬指定你自己的lookup接口(查找接口),這與Spring指定lookup接口略有不同。詳情參看這些類的javadocs

譯注,今天加班晚了點,到家時23:30了,可是今天還沒翻。進了家門,打開電腦,翻一小節再說,要不估計睡不着覺了。對於我自己的毅力,我還是有相當的認識的,比如:無論咳的多么嚴重,都能堅持抽煙,由此可見一斑。以上是玩笑。我的意志力並不強,但是意志薄弱也有意志薄弱的積極的正面的意義,比如我養成了每天翻點東西的習慣,哪怕就是再困、再餓、再累,也得翻譯一下,因為要是不翻譯的話,我就得跟自己的習慣作斗爭了,准確的說是和自己斗爭,而我卻又沒有與自己斗爭的念想,我根本打不過我自己,就這樣,我又翻了一小節

任意方法替換

還有一種方法注入方式,不如`lookup method`注入方式好用,可以用其他bean方法實現替換受管理的bean的任意方法。你可以跳過本節,當真的需要時再回來也是可以的。

在xml配置中,設置replaced-method元素,就可用其他實現來替換已經部署的bean中存在的方法實現。考慮下面的類,有一個我們要重寫的方法computeValue

public class MyValueCalculator { public String computeValue(String input) { // balbalba } // 其他方法... }

有個類實現了org.springframework.beans.factory.support.MethodReplacer接口,類中有新的方法定義

/**
 * 意味着用來重寫MyValueCalculator類中computeValue(String)方法的實現  */ public class ReplacementComputeValue implements MethodReplacer { public Object reimplement(Object o, Method m, Object[] args) throws Throwable { // get the input value, work with it, and return a computed result String input = (String) args[0]; ... return ...; } }

bean定義,用來部署的源類,要設置方法重寫,大概這么搞:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator"> <!-- arbitrary method replacement --> <replaced-method name="computeValue" replacer="replacementComputeValue"> <arg-type>String</arg-type> </replaced-method> </bean> <bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

可以在<replaced-method/>元素內設置一個或多個<arg-type/>元素來指明被替換方法的參數類型。只有被覆蓋的方法在類有重載,參數簽名才是必要的。為了方便,String類型的參數只需要其完全限定類型名稱的字串即可。比如,下面列出的均可匹配java.lang.String:

java.lang.String
String
Str

因為參數的數量基本就可以確定方法(重載的方法,基本上是參數數量有區別),此簡寫能大量減少打字,讓你僅打幾個字符就能匹配參數類型。 譯注,Spring是工業品質的框架,如此細微的人性化設計,值得學習

bean作用域

Spring bean定義時,實際上是創建類實例的配方。這個觀點非常重要,因為她意味着,通過一個配方,即可創建很多類的對象。

對於依據bean定義產生的bean,不僅可以控制依賴、設置對象的值,還可以對象作用域。這個手法強大而靈活,因為在配置過程中就可以可以控制的bean的作用域,無需在代碼層面去控制,用代碼去控制簡直就是煎熬。要部署的bean可有設置1個或多個作用域:開箱即用,Spring框架支持5中作用域,其中有三種只有用web-awareApplicationContext才能使用。

下面了列出的作用域開箱即用,你也可以自定義作用域

Table 5.3. Bean scopes

作用域 描述
單例singleton 默認的。一個bean定義,在一個IoC容器內只會產生一個對象。
prototype原型 一個bean定義會產生多個對象實例
request請求 一個bean定義產生的bean生命周期為一個HTTP請求;也就是,每一個HTTP請求都會根據bean定義產生一個對象實例。該作用域只有在Spring web上下文環境中才有效。
session會話 產生的bean生命周期在HTTP 會話期間。該作用域只有在Spring web上下文環境中才有效
gloabal session全局session 聲明周期為全局HTTP會話。通常使用portlet context時常用。該作用域只有在Spring web上下文環境中才有效。
application應用 生命周期與ServletContext一樣。該作用域只有在Spring web上下文環境中才有效

注意

Spring3.0起 多了一個作用域-thred,但它默認是未注冊的(不可用的意思?)。詳情請參看文檔去吧SimpleThreadScope。有關如何注冊該作用域和注冊自定義作用域,參看本章使用自定義作用域

單例作用域

單例bean只會產生一個實例,對於所有的請求,Spring容器都只會返回一個實例。

換句話說,當定義了單例bean,Srping容器只會創建一個實例,這個實例存儲在單例池中,單例池應該屬於緩存,接下來所有對於該單例bean的請求和引用,都將返回緩存中的對象。

Figure 5.2. 替換的文本可選的

Spring單例bean的概念,和四人幫GOF那本《設計模式》中定義的單例模式不同。GOF的單例是硬編碼級的對象作用域,因此導致每一個類加載器內會產生單例類的一個實例。Spring的單例恰如其名,在容器范圍內只會產生一個類實例。Spring中,bean默認的作用域都是單例作用域。使用xml 定義單例bean,像這樣:

<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- 和下面的寫法相等,因為單例作用域是默認的,所以這么寫有些畫蛇添足,意思就是廢話了 --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

prototype原型作用域

設置bean作用域為`prototype`,就是非單例,對於每次請求都將返回一個該類的新實例。也就是說,原型bean注入另一個bean,或者是請求原型bean,都是通過在容器上調用`getBean()`方法產生的。一般來說 ,原型bean用於有狀態bean,單例bean用於無狀態bean。

下圖示例了Srping原型作用域。一個數據訪問對象(DAO)通常不會配置成原型作用域,因為通常DAO不會持有任何會話狀態;因為作者偷懶,所以重用了上面單例示意圖。 替換的文本可選的

接下來看看如何在XML中定義原型bean:

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

和其他作用域相比,Srping並不管理原型bean的完整的生命周期:容器實例化,配置或者組裝原型獨享,注入給其他類,然后並未進一步記錄那個原型bean。因此,盡管對象的初始化回調方法會調用,不受scope影響,但是對於原型bean,銷毀回調不會被調用。客戶端代碼必須清理原型對象並且釋放原型bean持有的資源。為了讓Spring容器釋放原型bean持有的資源,可以用自定義的bean[post-processor](#beans-factory-extension-bpp), 它持有需要被清理bean的引用。

某種意義上,對於原型bean來說,Spring容器的角色就是替換了new 操作符。所有的生命周期管理,在經過實例化之后,都需要由客戶端來處理。(Spring 容器中bean的生命周期詳情,請參看本章5.6.1生命周期回調)

單例依賴原型

單例類依賴了原型類,要知道依賴在單例類初始化的時候就已經注入好了。因此,若你注入了一個原型bean給單例bean,將會是一個新的原型bean的實例注入了單例bean實例。原型bean實例將會是唯一的實例,再也不會為單例bean產生新的實例。

假若你需要單例bean在運行時重復的獲取新的原型bean實例。那就不能將原型bean注入給單例bean,因為那樣注入只會發生一次,就是發生在在Srping容器實例化單例bean並解析注入依賴時。如果需要多次獲取新的原型bean實例,參看本章5.4.6方法注入

Request, session, and global session scopes

`request`,`session`,`global session`作用域,只有在spring web `ApplicationContext`的實現中(比如`XmlWebApplicationContext`)才會起作用,若在常規Spring IoC容器中使用,比如`ClassPathXmlApplicationContext`中,就會收到一個異常`IllegalStateException `來告訴你不能識別的bean作用域

初始化web配置

為了支持`request,sesssion,global session`這種級別bean的作用域(web作用域bean),在定義bean之前需要一些初始化的小配置。(Spring標准作用域,包括單例和原型,無需此配置。)

如何配置要根據具體的Servlet環境

若使用 Spring Web MVC訪問這些作用域bean,實際上是使用Srping DispatcherServlet類或者DispatcherPortlet類處理request,則無需特別配置:DispatcherServlet 和 DispatcherPortlet已經暴露了所有的相關狀態。

若使用了Servlet 2.5的web容器,使用了非Spring的DispacherServlet處理請求(比如,JSF或者Struts),則需要注冊org.springframework.web.context.request.RequestContextListener ServletRequestListener。若使用的Servlet 3.0+,這些設置可以通過編程式方式使用WebApplicationInitializer接口完成。若使用的是較老的容器,增加下面配置添加到你的web應用的web.xml文件中:

<web-app>
    ...
    <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>

如果設置listener有問題的話,可以考慮使用RequestContextFilter。filter映射要根據web 應用配置來調整:

<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都是做相同的事兒,也就是綁定HTTPrequest對象到服務的Thread線程中,並開啟接下來 用到的session-scoped功能。

Request作用域

考慮下面這種bean定義: ```xml ``` Spring 使用該bean定義為每一次HTTP 請求創建一個新的`LoginAction`bean 的實例。也就是,`loginAction`bean作用域范圍在HTTP 請求級別。可以改變實例的內部狀態,多少實例都可以,因為根據此`loginAciton`bean定義創建的其他bean實例並不會看到這些狀態的改變;他們為各自的request擁有。當reqeust完成處理,request作用的bean就被丟棄了。

session作用域

考慮下面這種bean定義: ```xml ``` 在一個session會話期間,Spring容器使用`userPreferences`定義創建了一個`UserPreferences`bean的實例。換句話說`userPreferences`bean在HTTP Session會話期間有效。和`request-scoped`bean相類似,可以改變bean實例的內部狀態,不管bean創建了多少實例都可以,要知道,使用相同的`userPreferences`定義創建的其他的bean實例看不到這些狀態的改變,因為他們都是為各自的HTTP Session服務的。當HTTP Session最終被丟棄時,該session內的`session-scoped`作用域的bean實例也會被丟棄。

全局Session作用域

考慮下面這種bean定義: ```xml ``` 全局session作用域與標准HTTP [Session作用域](#beans-factory-scopes-session)類似,僅能應用於基於portlet的web應用的上下文環境中。portlet規范中定義的`global Session`概念是,在由單個portlet web應用創建的所有的的portlets中共享。全局session作用域的bean和`global portlet Session`全局portlet會話生命周期相同。 若是在標准的基於Servelt web應用中定義了全局session作用域bean,那么將會使用標准的Session作用域,不會報錯。

應用作用域

考慮下面這種bean定義: ```xml ``` Spring 容器使用該定義為整個web應用創建一個`AppPreferences`bean的實例。`appPreFerences`bean作用域是`ServeletContext`級別,存儲為一個常規的`ServletContext`屬性。這個Spring單例作用域有幾分相似,但是和單例作用域相比有兩個重要不同:1、他是每一個`ServeltContext`一個實例,而不是Spring`ApplicationContext`范圍。2、它是直接暴露的,作為`ServletContext`屬性,因此可見。

不同級別作用域bean之間依賴

Spring IoC容器不僅管理bean的實例化,也負責組裝(或者依賴)。如果想將HTTP request作用域bean注入給其他bean,就得給作用域bean(request或者session)注入一個AOP代理用來替換作用域bean。通過注入一個代理對象暴露於作用域bean相同的的接口,他是代理對象也能從相關作用域(request或者session)中檢索到真正的被代理對象,並委派方法調用實際對象的方法。

注意

不需要再單例或者原型bean內部使用<aop:scoped-proxy/>

下面的配置雖然簡單,但是重要的理解“為什么”和“如何搞”

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

為了創建一個代理,得在作用域bean定義內插入子元素<aop:scoped-proxy/>。詳情參看 “Choosing the type of proxy to create” 和 Chapter 34, XML Schema-based configuration.)requestsessionglobalSession , custom-scope,為什么這些級別的作用域需要<aop:scoped-proxy/>元素? 下面來做個小測驗,一個單例bean定義,對比一下,它如果要實現前面提到的作用域bean注入,該如何配置。(下面的userPreferencesbean,實際上並不完整)。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>

上例中,HTTP Session作用域beanuserPreferences注入給了單例beanuserManger。注意,userManagerbean是一個單例bean:每個容器只會實例化一個,他的依賴(本例中只有一個,userPreferencesbean)也僅會注入一次。也就是說,userManagerbean只能操作相同的userPreferences對象,就是注入的那一個。

將一個短生命周期作用域bean注入給長生命周期作用域bean,比如將HTTP Session作用域bean作為依賴注入給一個單例bean。然而,你需要一個userManager對象,在HTTP Session會話期間,需要與session同生命周期的對象userPreferences。 因此,容器會創建一個對象,該對象擁有和UserPreferences完全相同的public接口並暴露所有的public接口。,該對象能根據作用域機制獲取真真的UserPreferences對象。容器會將這個代理對象注入給userManagerbean,userManager類則渾然不知這貨居然是個代理。樣例中,當UserManager實例調用依賴UserPreferences對象上的方法時,,實際上調用的是代理對象上的方法。代理對象從 Session范圍內獲取真正的UserPreferences對象,並將在代理對象上方法的調用“呼叫轉移”給檢索到的真正的UserPreferences對象。

將一個request,session,globalSession作用域bean注入給其他作用域bean,下面是正確的、完整的配置

<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>
選擇代理類型

使用``元素為bean 創建代理時,Spring 容器默認使用`CGLIB`類型創建代理。

注意

CGLIB代理只會攔截public方法調用。非public方法不會“呼叫轉移”給實際的作用域bean。

還有個選擇,通過配置,使Spring容器為這些作用域bean創建標准的JDK interface-based代理,設置<aop:scoped-proxy/>元素proxy-target-class屬性的值為false即可。使用標准JDK接口代理好處是無需引入第三方jar包。然而,作用域bean 至少實現一個接口,需要注入作用域bean的類則依賴這些接口。

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

For more detailed information about choosing class-based or interface-based proxying, see Section 9.6, “Proxying mechanisms”. 關於如何選擇class-basedinterface-based代理,詳情參看Section 9.6, “Proxying mechanisms”.

自定義作用域

bean的作用域機制是可擴展的;可以定義自己的作用域,甚至重新定義已存在的作用域,經管后者不推薦,並且,不能重寫內置單例作用域和原型作用域。

創建自定義作用域

實現`org.springframework.beans.factory.config.Scope`接口,就可以將自定義作用域集成到Srping容器中,本章主要將如何實現該接口。如何實現自定義作用域,參看Spring內置的作用域實現和`Scope`類的javadocs,javadocs中解釋了有關需要實現的方法的細節。

Scope接口共有4個方法用於從作用域獲取對象、從作用域刪除對象、銷毀對象(應該是指作用域內,英文檔中未提到)

下面的方法作用是返回作用域中對象。比如,session作用域的實現,該方法返回session-scoped會話作用域bean(若不存在,方法創建該bean的實例,並綁定到session會話中,用於引用,然后返回該對象)

Object get(String name, ObjectFactory objectFactory)

下面的方法作用是從作用域中刪除對象。以session作用域實現為例,方法內刪除對象后,會返回該對象,但是若找不到指定對象,則會返回null

Object remove(String name)

下面的方法作用是注冊銷毀回調函數,銷毀是指對象銷毀或者是作用域內對象銷毀。銷毀回調的詳情請參看javadocs或者Spring 作用域實現。

void registerDestructionCallback(String name, Runnable destructionCallback)

下面的方法,用於獲取作用域會話標識。每個作用域的標識都不一樣。比如,session作用域的實現中,標識就是session標識(應該是指sessionId吧)

String getConversationId()
使用自定義作用域

可能是HelloWorld,或者是更多的自定義的Scope實現,得讓Spring知道新寫的作用域。下面的方法就是如何注冊新的作用域到Spring 容器的核心方法:

void registerScope(String scopeName, Scope scope);

This method is declared on the ConfigurableBeanFactory interface, which is available on most of the concrete ApplicationContext implementations that ship with Spring via the BeanFactory property. 此方法聲明在ConfigurableBeanFactory接口中,該在大部分ApplicationContext具體實現中都是可用的,通過BeanFactor屬性設置

registerScope(..)方法第一個參數是作用域名稱,該名稱具有唯一性。比如Spring容器內置的作用域singletonprototype。第二個參數是自定義作用域實現的實例,就是你想注冊的、使用的那個自定義作用域。

寫好了自定義作用域的實現,就可以像下面那樣注冊它了: 注意

下面的SimpleThreadScope作用域,是Spring內置的,但是默認並未注冊到容器中。 你自定義的作用域實現,應該也使用相同的代碼來注冊。

Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);

接下來是創建一個bean定義,該定義要遵守自定義作用域的規則

<bean id="..." class="..." scope="thread">

自定義作用域的實現,不局限於編程式注冊。也可以使用CustomScopeConfigurer類聲明式注冊

*譯注*,編程式就是指硬編碼,hard-code,聲明式就是指配置,可以是xml可以是注解總之無需直接使用代碼去撰寫相關代碼。不得不說,*編程式和聲明式*與*硬編碼和配置*相比,更加高端大氣上檔次。技術人員尤其要學習這種官方的、概念性的、抽象的上檔次的語言或者說式地道的表達,假若談吐用的全是這種詞匯,逼格至少提升50%,鎮住其他人(入行時間不長的同行,或者面試官)的概率將大大提升。當然了,和生人談吐要用高逼格詞匯,比如*聲明式*,*編程式*,然而和自己人就要用人話了,比如*硬編碼*,*xml配置*,因為他們得能先聽懂才能干活。
總之,**裝逼用官話,聊天用人話**,閑話少絮,看如何聲明式注冊(因為此處要裝逼,人話是看如何xml)

blablablab

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

注意

When you place aop:scoped-proxy/ in a FactoryBean implementation, it is the factory bean itself that is scoped, not the object returned from getObject(). 如果在FactoryBean實現中設置了<aop:scoped-proxy/>,表示是工廠bean他本身的作用域,並不是getObject()返回的對象的作用域。TODO

Customizing the nature of a bean自定義bean的xxx擦這個nature該怎么翻

生命周期回調函數

Spring容器可以控制bean的生命周期,通過實現Spring`InitializingBean`和`DisposableBean`接口。容器會調用`InitializingBean`接口的`afterPropertiesSet()`方法,也會調用`DisposableBean`接口的`destroy()`方法。,也就是運行bean自定義的初始化方法和銷毀方法。

注意

Tip JSR-250中,在現代Spring應用中,一般都是用@PostConstruct@PreDestroy注解定義生命周期回調函數。使用注解的話,你的bean就無需和Spring API耦合了。,詳情參看Section 5.9.7, “@PostConstruct and @PreDestroy” 如果不想用JSR-250,但又想解耦(Spring API),可以在定義對象的配置中指定init-methoddestroy-method

Spring使用BeanPostProcessor實現類處理所有的回調接口並調用相應的方法,接口由Spring 負責查找。若需要自定義功能或其他生命周期行為,Spring並未提供開箱即用的支持,但是可以自己實現BeanPostProcessor類。詳情參看"Section 5.8, “Container Extension Points”

除了initializationdestruction方法,Spring bean也可以實現Lifecycle接口,這些接口可以參與Spring容器生命周期的startupshutdown過程。

本章講解生命周期回調接口。

初始化回調

`org.springframework.beans.factory.InitializingBean`接口類的作用是,在容器設置bean必須的屬性之后,執行初始化工作。`InitializingBean`接口中只有一個方法: ```java void afterPropertiesSet() throws Exception; ```

推薦,盡量不用InitializingBean接口,因為這將導致不必要的與Spring的耦合。還有更好的辦法,使用@PostConstruct注解,或者指定一個POJO的initialization方法。XML配置元數據中,使用init-method屬性用來指定,其值為初始化方法名,初始化方法得是一個無參無返回值(void)方法。如果使用java Config,得在@Bean注解中使用initMehtod屬性 ,詳情參看 the section called “Receiving lifecycle callbacks”。看代碼

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean { public void init() { // do some initialization work } }

和下面的效果相同,但上面的沒有耦合Spring。

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } }
銷毀回調

實現`org.springframework.beans.factory.DisposableBean`接口,作用是Spring銷毀bean時調用該方法。 `DisposableBean`接口只有一個方法: ```java void destroy() throws Exception; ``` 和上面初始化函數一樣,推薦你不要使用`DisposableBean`回調接口,因為會產生不必要的耦合之類的balbalbal。還是和上面一樣,能使用 [`@PreDestroy`](#beans-postconstruct-and-predestroy-annotations)注解或者指定一個spring bean定義支持的方法TODO??若使用XML配置,可是使用``元素的`destroy-method`屬性來完成該設置。若是使用Java config,可以使用`@Bean`注解的`destroyMethod`屬性來完成銷毀回調設置。[see the section called “Receiving lifecycle callbacks”](#beans-java-lifecycle-callbacks)。看樣例: ```xml ``` ```java public class ExampleBean {

public void cleanup() {
    // do some destruction work (like releasing pooled connections)
}

}

和下面代碼效果一樣,但是上面的代碼不和Spring耦合
```xml
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work (like releasing pooled connections) } }

注意

<bean>元素的destroy-method屬性可以指定一個特別的值,設置該值后Spring將會自動探測指定類上的public close或者shutdown方法。這個設置自動探測銷毀方法的屬性,也可以設置給<beans/>元素的default-destroy-method屬性,用來設置<beans>元素內的所有的<bean> 自動探測銷毀方法,詳情參看section called “Default initialization and destroy methods”。注意,在Java config配置元數據中,這種自動探測是默認的。

譯注,現在是羊年除夕夜23:40,再過30分鍾,公司服務器上的一些定時器就要開始運行了。不知道還會不會線程掛起了,多線程中使用網絡輸出流時如果發生斷網,線程則會處於阻塞狀態,然后就沒有然后一直阻塞,已經修改過了。外面的煙花炮仗聲逐漸的密集了起來,放炮仗,污染太重了,國家抑制的手段就和抑制煙草手段一樣,重稅。心亂了,不能專心翻譯了。

默認的初始化函數和銷毀函數

若不是使用`InitializingBean`和`DisposableBean`接口實現初始化和銷毀回到方法,通常使用規范的方法名比如`init`,`initialize()`,`dispose()`等等。理論上,生命周期回調方法名的規范性,應該貫穿於整個項目中,所有的開發者都應該使用相同的方法名保持一致性。*譯注,編碼規范,Spring最講究這個了*

可以配置容器查找所有bean的初始化回調和銷毀回調,當應用類中的初始化回調方法命名為init(),就不需要在bean定義中配置init-method="init"屬性。Spring IoC容器在bean初始化時調用init()回調。該功能強制初始化和銷毀回調方法命名的規范性。

Suppose that your initialization callback methods are named init() and destroy callback methods are named destroy(). Your class will resemble the class in the following example. 假設,初始化回調方法命為init(),銷毀回調方法命名為destroy()。應該和下面的樣例差不多:

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>

在頂級<bean/>元素中定義了default-init-method屬性,使Spring Ioc 容器解析bean中名為init的方法為初始化回調方法。當bean創建實例並組裝時,若bean類中有個一個init()方法,該初始化回調會在合適的時間調用。

聯合混合使用多種生命周期回調機制

Spring2.5 以后,控制bean生命周期行為,有三種生命周期回調機制,或者說是三種方式實現:[InitializingBean](#beans-factory-lifecycle-initializingbean) 和 [DisposableBean](#beans-factory-lifecycle-disposablebean) 回調接口;自定義`init()`和`destroy()`方法;[ @PostConstruct and @PreDestroy ](#beans-postconstruct-and-predestroy-annotations)注解。這些方式可以混合使用。

注意

如何一個bean上配置了多種生命周期回調機制,並且每種機制都使用了不同的方法,那么所有的回調方法都會按次序執行。然而,如果配置了相同的方法名,比如init()方法作為初始化方法,該方法在多種生命周期回調機制中都有配置,但是,該方法只會執行一次。

在一個bean中,配置多種生命周期回調機制,每種機制使用了不同的初始化方法,會按照下列次序調用:

  • @PostConstruct注解的方法
  • InitializingBean回調接口中的afterPropertiesSet()方法
  • 自定義的init()方法

銷毀回調也使用相同的次序

  • @PreDestroy注解的方法
  • DisposableBean回調接口中的destroy()方法
  • 自定義的 destroy()方法
容器啟動和關閉回調

`Lifecycle`接口定了對象有自己生命周期需求的必須的方法(比如啟動停止某些后台處理) ```java public interface Lifecycle {

void start();

void stop();

boolean isRunning();

}

任何Spring管理的對象都可以實現此接口。當`ApplicationContext `接口啟動和關閉時,它會調用本容器內所有的`Lifecycle`實現。通過`LifecycleProcessor`來調用,
```java
public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

注意LifecycleProcessor接口繼承了Lifcycle接口。同時,增加了2個方法,用於處理容器的refreshedclosed事件。

startupshutdown方法調用次序非常重要。若兩個對象有依賴關系,依賴方會在依賴啟動之后啟動,會在依賴停止之前停止。然而,有時依賴並不直接。也許你僅知道某些類型對象優先於另外一種類型啟動。此場景中,SmartLifecycle接口也許是個好主意,該接口有個方法getPhase(),此方法是其父接口Phased中的方法:

public interface Phased { int getPhase(); } public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }

啟動時,最低層次的phase最先啟動,停止時,該次序逆序執行。因此,若對象實現了SmartLifecycle接口,它的getPhase()方法返回Integer.MIN_VALUE,那么該對象最先啟動,最后停止。若是返回了Integer.MAX_VALUE,那么該方法最后啟動最先停止(因為該對象依賴其他bean才能運行)。關於phase的值,常規的並未實現SmartLifecycle接口的Lifecycle對象,其值默認為0。因此,負phase值表示要在常規Lifecycle對象之前啟動(在常規Lifecycyle對象之后停止),使用 正值則恰恰相反。

如你所見,SmartLifecyclestop()方法有一個回調參數。所有的實現在關閉處理完成后會調用回調的run()方法。TODO 。它相當於開啟了異步關閉功能,和LifecycleProcessor接口默認實現DefaultLifecycleProcessor類的異步,該類會為每個phase的回調等待超時。每個phase默認的超時是30秒。可以重寫該類默認的實例,該類在容器內默認bean名稱是lifecycleProcessor。如果你僅想修改超時,這么寫就足夠了。

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <!-- timeout value in milliseconds --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean>

As mentioned, the LifecycleProcessor interface defines callback methods for the refreshing and closing of the context as well. The latter will simply drive the shutdown process as if stop() had been called explicitly, but it will happen when the context is closing. The refresh callback on the other hand enables another feature of SmartLifecycle beans. When the context is refreshed (after all objects have been instantiated and initialized), that callback will be invoked, and at that point the default lifecycle processor will check the boolean value returned by each SmartLifecycle object’s isAutoStartup() method. If "true", then that object will be started at that point rather than waiting for an explicit invocation of the context’s or its own start() method (unlike the context refresh, the context start does not happen automatically for a standard context implementation). The "phase" value as well as any "depends-on" relationships will determine the startup order in the same way as described above.

TODO 書接前文,LifecycleProcessor接口也定義了容器的refreshingclosing事件。后者會驅動shutdown處理,就像是明確的調用了stop()方法,但是它是發生在容器關閉期間。refresh回調開啟了SmartLifecyclebean的另一個功能 。當上下文環境刷新時(在所有的對象實例化和初始化之后),則會調用refresh回調,同時,默認的lifecycle processor檢查每個SmartLifecycle對象的isAutoStartup()方法返回的布爾值。若為true,對象則會在那時啟動,而不是等待容器顯示調用之后或者是他自己的start()方法調用之后(這和容器刷新不同,標准的容器實現啟動不會自動發生)。phase值和depends-on關系一樣,都使用了相同的方法決定了的啟動次序。

非web應用中安全的關閉Spring IoC容器

![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png) > 本章適用於非web應用。基於Spring web的應用的`ApplicationContext`實現類,已經提供了支持,用於在應用關閉時安全的關閉Spring IoC容器。

在一個非web應用的環境中使用Spring IoC容器;比如,在一個富客戶端桌面的環境中;得在JVM中注冊一個shutdown鈎子。這么做是為了安全的關閉,在關閉時保證所單例bean的相關的destroy方法會被調用,這樣就可以釋放所有的資源。當然了,你必須得正確的配置和實現銷毀回調。

要注冊shutdown鈎子,得調用registerShutdownHood()方法,該方法在AbstractApplicationContext類中。

import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Boot { public static void main(final String[] args) throws Exception { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext( new String []{"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... } }

ApplicationContextAware and BeanNameAware

`org.springframework.context.ApplicationContextAware`接口實現類的實例將會持有`ApplicationContext`的引用: ```java public interface ApplicationContextAware {

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}


因此可以編程式的使用`ApplicationContext`手動的創建bean,通過`ApplicationContext`接口或者是該接口的子類,比如`ConfigurableApplicationContext`,該類還增加了方法。用途之一是編程式的檢索bean,有時非常有用。然而,大多數情況下,要避免編程式檢索bean,這樣的話你的代碼就會和Spring耦合,這不是IoC的風格,Ioc的風格是協作類作為bean的屬性。`ApplicationContext`類的其他方法提供了文件資源的訪問接口、發布應用事件、訪問`MessageSource`消息資源。這些附加的功能請參看[Section 5.15, “Additional Capabilities of the ApplicationContext”](#context-introduction)

自Spring2.5起,可以使用自動裝配獲取`ApplicationContext`引用。傳統的`constructor`和`byType`自動裝配模式(詳情參看 [Section 5.4.5, “Autowiring collaborators”](#beans-factory-autowire)能為構造參數或者`setter`方法提供一個`ApplicationContext`類的依賴注入。為了更加靈活,還增加了自動注入的注解功能,它能自動注入屬性和自動注入多參數方法。使用注解,`ApplicationContext`可以自動注入到`ApplicationContext`類型的屬性、構造參數、方法參數。詳情參看[Section 5.9.2, “@Autowired”](#beans-autowired-annotation).

`org.springframework.beans.factory.BeanNameAware`接口的實現類,若是由`ApplicationContext`創建了該類的實例,該實例將會持有相關的對象定義的引用。
```java
public interface BeanNameAware {

    void setBeanName(string name) throws BeansException;

}

The callback is invoked after population of normal bean properties but before an initialization callback such as InitializingBean afterPropertiesSet or a custom init-method. TODO這個回調在設置屬性之后調用,但是在initialization回調之前,比如InitializingBeanafterPropertiesSet或者 自定義的init-method

Other Aware interfaces

Besides ApplicationContextAware and BeanNameAware discussed above, Spring offers a range of Aware interfaces that allow beans to indicate to the container that they require a certain infrastructure dependency. The most important Aware interfaces are summarized below - as a general rule, the name is a good indication of the dependency type:

除上面討論過的ApplicationContextAwareBeanNameAware,Spring提供了一些了Aware接口,這些接口可以提供容器中相關的基礎(SpringAPI)依賴。最重要的Aware接口參看下面的摘要,命名相當規范,看名字就能知道依賴類型: Table 5.4. Aware interfaces
名稱 | 注入依賴 | 詳情 ---- | ---- | ------ ApplicationContextAware | ApplicationContext | Section 5.6.2, “ApplicationContextAware and BeanNameAware” ApplicationEventPublisherAware | 發布事件 | Section 5.15, “Additional Capabilities of the ApplicationContext” BeanClassLoaderAware | 加載bean的類加載器 | Section 5.3.2, “Instantiating beans”BeanFactoryAware | 聲明BeanFactory | Section 5.6.2, “ApplicationContextAware and BeanNameAware” BeanNameAware | 生命bean 的名字 | Section 5.6.2, “ApplicationContextAware and BeanNameAware” BootstrapContextAware | Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts | Chapter 26, JCA CCI LoadTimeWeaverAware | Defined weaver for processing class definition at load time | Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework” MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization) | Section 5.15, “Additional Capabilities of the ApplicationContext”NotificationPublisherAware | Spring JMX notification publisher | Section 25.7, “Notifications” PortletConfigAware | Current PortletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 20, Portlet MVC Framework PortletContextAware | Current PortletContext the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 20, Portlet MVC Framework ResourceLoaderAware | Configured loader for low-level access to resources | Chapter 6, Resources ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 17, Web MVC framework

注意,這些接口的用法使代碼與Spring API耦合,這不符合IoC風格。同樣,除非有需求的基礎bean才使用編程式訪問容器。

Spring Bean的繼承

Spring bean定義包含各種配置信息,包括構造參數,屬性值,容器特定信息例如初始化方法、靜態工廠方法等等。Spring子bean定義繼承父bean定義配置。子bean能覆蓋值,若有需要還能增加其他配置。使用繼承能少打好多字。這是模板的一種形式,講究的就是效率。

編程式的方式使用ApplicationContext場景,子bean的定義代表ChildBeanDefinition類。大多數用戶不需要使用如此底層的SpringAPI,通常是使用類似ClassPathXmlApplicationContext的bean聲明。若用XML配置,通過parent屬性表示子bean定義,指定父bean的標識作為parent屬性值。

<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中未指定class屬性,則子bean集成父bean的class屬性,子bean可以重寫覆蓋此屬性。若要覆蓋重寫class屬性,子bean的class類型必須兼容父bean的class,也就是,子bean必須能接收父bean的屬性值。

其他的屬性也是通常取自子bean的配置:depends on, autowire mode, dependency check, singleton, lazy init.

前面樣例中,使用abstract屬性指定了父bean為抽象定義。如父bean中未指定class,則必須指定父bean為抽象bean。看代碼:

<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不能實例化,因為她不完整,是抽象的bean,作為子bean的純模板時,它是非常有用的。試試通過屬性引用或者使用getBean()方法調用該bean,會拋錯。容器內部的preInstantiateSingletons()方法會忽略抽象bean。 注意

ApplicationContext類默認會預先實例化所有的單例bean。因此,如果有做模板用的父bean,父bean定義中指定了classs屬性,則必須指定abstracttrue,這是非常重要的,否則容器會預先實例化該bean。

容器擴展點

通常開發者無需自己實現`APplicationContext`,而是使用插件擴展Spring IoC容器,插件是某些指定的集成接口的實現。下面記賬講解這些集成接口。

使用BeanPostProcessor自定義bean

`BeanPostProcessor`接口定義了實例化邏輯、依賴邏輯等回調方法,即可以自定義也可以覆蓋容器默認方法。若果要在Spring容器完成實例化、配置、初始化bean之后執行自定義邏輯,則以插件方式實現`BeanPostProcessor`。

可以配置多個BeanPostProcessor實例,可以設置BeanPostProcessorsorder屬性來控制其執行次序。讓BeanPostProcessor實現Ordered接口,就能設置次屬性。如果使用自定義BeanPostProcessor,也得考慮實現Ordered接口。更多的細節,參閱BeanPostProcessorOrdered接口的javadocs。也可以查閱programmatic registration of BeanPostProcessors譯注,SPring參考手冊中這個鏈接確實沒有

注意

NOTE BeanPostProcessors操作bean的實例;也就是,Spring IoC容器實例化bean的實例時BeanPostProcessors開始運行。

BeanPostProcessors在各自容器內有效。當使用容器繼承時,BeanPostProcessors缺不會繼承。如果在某容器內定義了BeanPostProcessor,近在本容器中生效。或句話說,一個容器中的bean不會使用另一個容器內的BeanPostProcessor處理,繼承的容器也不行。

要改變bean定義(也就是,bean定義的藍圖,譯注藍圖應該是指各種配置元數據,比如xml、注解等),你得使用BeanFactorPostProcessor,詳情參看in Section 5.8.2, “Customizing configuration metadata with a BeanFactoryPostProcessor”

org.springframework.beans.factory.config.BeanPostProcessor接口有2個回調方法組成。當這樣的類在容器內注冊為post-processor,容器創建所有bean,在容器初始化方法(比如InitializingBeanafterProperieSet()方法和其他所有的聲明的init方法)和所有bean 初始化回調之前,運行post-processor回調。

ApplicationContext自動探測在配置元數據中定義的BeanPostProcessorApplicationContext注冊這些bean為post-processors,這樣就可以在bean創建之前調用。Bean的post-processors可以像其他bean那樣部署到容器里。

注意,在configuration類中,使用@Bean工廠方法聲明BeanPostProcessor,該工廠方法的返回類型必須是該實現類或者至少得是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚的標識出post-processor。否則,ApplicationContext不會開啟根據類型自動探測。因為BeanPostProcessor需要盡早的實例化,這樣在容器中即可用於其他bean的初始化,因此這種盡早的類型探測至關重要。

注意 編程式注冊BeanPostProcessor 盡管推薦的BeanPostProcessor的注冊方式是通過ApplicationContext的自動探測機制,但是也可以使用ConfigurableBeanFactory類調用其addBeanPostProcessor實現編程式的注冊。編程式注冊是非常有用的,比如用於在注冊之前實現等價的邏輯,再比如跨容器復制post processors。注意使用編程式注冊BeanPostProcessors並不會遵守Ordered接口的次序。注冊的順序就是執行的次序。此外還得記得,編程式的注冊BeanPostProcessors會在自動探測注冊的BeanPostProcessors之前處理,無論自動探測注冊的BeanPostProcessors指定了多么優先的次序。 注意 BeanPostProcessor和AOP的自動代理 Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessors and beans that they reference directly are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all BeanPostProcessors are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessors nor the beans they reference directly are eligible for auto-proxying, and thus do not have aspects woven into them. 容器會特殊對待BeanPostProcessor接口。所有的BeanPostProcessors及引用了BeanPostProcessors的bean會在啟動時實例化,作為ApplicationContext特殊的啟動階段。接下來,所有的BeanPostProcessors都會按照次序注冊到容器中,在其他bean使用BeanPostProcessors處理時也會使用此順序。因為AOP的auto-proxying自動代理是BeanPostProcessor的默認實現,它既不引用BeanPostProcessors也不引用其他bean,不會發生auto-proxying自動代理,因此不會有切面織入。TODO

對於BeanPostProcessor類型的bean,會看到這樣一條日志:"Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)" 注意,如果有bean通過自動注入或者@Resource(可能會導致自動注入)注入到BeanPostProcessor,在使用類型匹配檢索依賴bean時Spring也許會訪問到不期望的bean,導致生成不合適的auto-proxying自動代理或者其他post-processing。舉個栗子,如果使用@Resouce依賴注解,而且field/setter上注解的名字和bean中聲明名字不一致時,Spring將會使用類型匹配訪問其他bean。

下面 示例中講解了在ApplicationContext中如何撰寫、注冊、使用BeanPostProcessors

栗子:Hello World,BeanPostProcessor風格 第一個示例,講解基礎用法。栗子展示了一個自定義BeanPostProcessor實現,功能是在容器創建bean時,調用每一個bean的toString()方法並輸出到控制台。 上干活,fuck goods

package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.BeansException; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; // we could potentially return any object reference here... } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 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是如何定義的。它甚至沒有名字,因為它能像其他bean那樣依賴注入。(上面的配置中,使用Groovy script創建了個bean。Spring動態語言支持的詳細講解參看Chapter 29, Dynamic language support

下面的java應用使用上面的配置和代碼執行,

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

將會輸出: Bean messenger created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961

Example: The RequiredAnnotationBeanPostProcessor 對於擴展Spring IoC容器,使用回調函數或者注解聯結一個自定義BeanPostProcessor實現類是常用的手段。例如Spring的RequiredAnnotationBeanPostProcessor,是個BeanPostProcessor實現類,spring內置,作用是確保Spring bean定義上的帶注解的JavaBean屬性確實被注入了值。

使用BeanFactoryPostProcessor自定義配置元數據

接下來的擴展點講一講`org.springframework.beans.factory.config.BeanFactoryPostProcessor`。此接口的語法和`BeanPostProcessor`類似,有一個主要的不同之處:`BeanFactoryPostProcessor`操作bean的配置元數據;也就是,Spring IoC容器允許`BeanFactoryPostProcessor`讀取配置元數據並且在容器實例化bean之前可能修改配置。

可以配置多個BeanFactoryPostProcessors,通過設置order屬性控制它們的執行次序。BeanFactoryPostProcessor若是實現了Ordered接口,則可設置該屬性。若是自定義BeanFactorPostProcessor,同時得考慮實現Ordered接口。詳情參閱BeanFactoryPostProcessor的javadocs。

注意

NOTE 如果要改變bean實例(根據配置元數據創建的對象),那么就需要使用BeanPostProcessor(上一章描述的in Section 5.8.1, “Customizing beans using a BeanPostProcessor”)。當使用BeanFactoryPostProcessor處理實例時(使用BeanFactory.getBean()方法),如此早的處理bean實例,違反了標准的容器生命周期。通過bean post processing也許會引起負面影響。 BeanFactoryPostProcessors的作用域也是在各自的容器內。如果使用容器繼承,這一點也是應該注意的。如果在某容器內定義了BeanFactoryPostProcessor,則僅應用於本容器。某容器內的bean定義,不會使用另一個容器的BeanFactoryPostProcessors處理,容器之間有繼承關系也不行。

為了讓配置元數據的改變應用,聲明在ApplicationContext內的bean工廠post-processor都是自動執行。Spring包含一系列的預先定義的bean工廠post-processors,比如PropertyOverrideConfigurerPropertyPlaceholderConfigurer。也可以使用自定義BeanFactoryPostProcessor,比如注冊一個自定義屬性編輯器。

ApplicationContext自動探測BeanFactoryPostProcessor接口的實現類。容器使用這些bean作為bean工廠post-processors。可以像其他bean那樣將post-processor部署在容器內。

注意

NOTE 若使用BeanPostProcessors,通常不會給BeanFactoryPostProcessors配置延遲初始化。如果沒有其他bean引用BeanFactoryPostProcessor,則post-processor根本不會實例化。因此設置延遲初始化將會被忽略,BeanFactoryPostProcessor將會及時實例化,甚至在<beans/>元素設置了default-lazy-init屬性為true也不行。

Example: the Class name substitution PropertyPlaceholderConfigurer

可以使用`PropertyPlaceholderConfigurer`將bean的屬性值使用標准的Java Properties格式定義在一個單獨的文件中。這樣可以將應用的自定義環境配置屬性隔離出來,比如數據庫URLs和密碼,這樣就降低了修改容器內XML配置或者Java 代碼的的復雜性和風險。

考慮下面的XML配置片段,使用了placeholder值定義了DataSource。樣例展示了一個外部的Properties文件的屬性配置。運行時,PropertyPlaceholderConfigurer會應用到配置元數據中,替換指定格式的placeholders,格式為${property-name},這樣的格式與Ant/log4j/JSP EL風格相同。

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

在標准java Properties格式文件中實際的值:

jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root

因此,字串${jdbc.username}在運行時賦值為sa,其他的${key}都會被替換為文件中與key對應的值。PropertyPlaceholderConfigurer檢查bean定義中大多數的placeholders占位符,placeholder的前綴和后綴都是自定義的。

使用Spring2.5引入的上下文命名空間,就可以用一個專用配置元素配置屬性placeholders占位符。可以指定多個locations,多個locations使用,逗號分割。

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

PropertyPlaceholderConfigurer不僅僅檢索指定的Properties文件。默認情況,若是在指定的Properties配置文件中找不到指定的屬性property,也會檢查Java 的系統屬性System properties。通過設置systemPropertiesMode屬性的值,定義默認查找行為,該屬性值有幾個取值:

  • never:不檢查系統屬性
  • fallback:如果未在指定文件中解析出屬性值,則檢查系統屬性。此項為默認行為。
  • override:先檢查系統屬性。系統屬性會覆蓋其他配置文件中的屬性。

PropertyPlaceholderConfigurer更多詳情參看javadocs

注意

TIP 可以使用PropertyPlaceholderConfigurer替換類名,有時,某些類在運行時才能確定,那么這將非常有用。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.foo.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>

若類在運行時期間不能解析為合法類,ApplicationContext創建非延遲初始化bean的preInstantiateSingletons()期間拋錯誤,

Example: the PropertyOverrideConfigurer

`PropertyOverrideConfigurer`,是另一個ben工廠的`post-processor`,類似於`PropertyPlaceholderConfigurer`,但是有不同之處,bean源定義可以設置默認值或者根本不設置值。若一個`overriding Properties`文件不包含某個bean屬性,就使用默認的上下文定義。

注意bean定義並不知道它會被重寫,所以使用了重寫配置在XML配置中並不直觀。如果有多個PropertyOverrideConfigurer實例為相同的bean屬性配置了不同的值,最后一個實例配置生效。

Properties文件配置格式如下

beanName.property=value

舉例:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

上述文件中的配置,將會賦值給在容器中的定義的bean的相應屬性 ,bean的名字是datasource,有driver屬性和url屬性

Compound property names are also supported, as long as every component of the path except the final property being overridden is already non-null (presumably initialized by the constructors). In this example…

同樣支持復合屬性,屬性路徑可以要多長有多長,但是屬性不能為null(),看樣例:

foo.fred.bob.sammy=123

bean foo有屬性fred,fred有屬性bobbob有屬性sammysammy賦值為123

注意

Note 指定重寫值都是字面值;不會解析為bean引用。就算是指定的值,在XML的bean定義中bean的名字,也不會解析為該引用,而是解析為字面值。

使用Spring 2.5中引入的上下文命名空間,可以為配置屬性指定專用配置元素

<context:property-override location="classpath:override.properties"/>

使用FactoryBean自定義實例化邏輯

對象實現`org.springframework.beans.factory.FactoryBean`接口,則成為它本身的工廠。

FactoryBean接口是Spring IoC容器實例化邏輯的擴展點。假如初始化代碼非常復雜,此時使用java編碼比使用XML配置更容易表達。這種場景中,就可以自定義FactoryBean,在類中撰寫復雜的初始化程序,並將其作為插件加入到容器中。

FactoryBean接口有3個方法:

  • Object getObject():返回本工廠創建的對象實例。此實例也許是共享的,依賴於該工廠返回的是單例或者是原型。
  • boolean isSingleton():如果FactoryBean返回的是單例,該方法返回值為true,否則為false
  • Class getObjectType():返回對象類型。對象類型是getObject()方法返回的對象的類型,如果不知道的類型則返回null。

FactoryBean概念和接口在Spring框架中大量使用。Spring內置的有超過50個實現。

當使用ApplicationContextgetBean()方法獲取FactoryBean實例本身而不是它所產生的bean,則要使用&符號+id。比如,現有FactoryBean,它有id,在容器上調用getBean("myBean")將返回FactoryBean所產生的bean,調用getBean("&myBean")將返回FactoryBean它本身的實例。

基於注解的把配置元數據

**注解比XML好么?** ```text 注解比XML好么,簡單的說得看情況。詳細的說,各有優缺點。因為定義的方式,注解在聲明處提供了大量的 上下文信息,所以注解配置要更簡潔。然而,XML擅長在不接觸源碼或者無需反編譯的情況下組裝組件。 雖然有這樣的爭議:注解類不再是`POJO`,並且配置更加分散難以控制, 但是還是有人更喜歡在源碼上使用注解配置。

無論選擇哪一樣,Spring都能很好的支持,甚至混合也行。值得指出的是, 使用[JavaConfig](#beans-java)選項,Spring能在不接觸目標組件源碼的情況下 無侵入的使用注解,這可以通過IDE完成 Spring Tool Suite


對於XML配置,還有另外一個選擇,基於注解的配置,它是依賴於字節碼元數據,替代XML組裝組件。碼農碼畜可以使用注解替代XML描述bean的組裝,開發者將配置撰寫到組件類上,使用注解標注相關的類、方法、域上。就像前面提到的 [in the section called “Example: The RequiredAnnotationBeanPostProcessor”](#beans-factory-extension-bpp-examples-rabpp),使用`BeanPostProcessor`聯結注解是常見的擴展Spring IoC容器的手段。舉個栗子,Spring2.0引入的通過[`@Required`](#beans-required-annotation)注解強制檢查必須屬性值。Spring 2.5采用了類似的手法使用注解處理依賴注入。本質上,`@Autowired`注解提供了相同的能力,在這一章有詳解[Section 5.4.5, “Autowiring collaborators”](#beans-factory-autowire),但是`@Autowired`提供了更細粒度的控制和更強的能力。Spirng 2.5也增加了對JSR-250注解的支持,比如`@PostConstruct`,`@PreDestory`。Srping3.0增加支持了JSR-330(JAVA依賴注入)注解,這些注解在`javax.inject`包內,例如`@Inject`和`@Named`。詳情參看那些注解的[相關章節](#beans-standard-annotations)。

![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png)  
>**注意**
> 注解注入在XML注入*之前*執行,因此同時使用這兩種方式注入時,XML配置會覆蓋注解配置。

同樣的Spring風格,就像特別的bean定義那樣注冊他們,但是也能像下面這樣隱式注冊(注意包含context namespace上下文命名空間)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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>

(隱式注冊的post-processors包括AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor,還有前面提到的RequiredAnnotationBeanPostProcessor)

注意

注意 <context:annotation-config/>僅會檢索它所在的應用context上下文中bean上的注解。也就是,如果在WebApplicationContext中為DispatcherServlet設置<context:annotation-config/>,它僅會檢查controllers@Autowired的bean,並不會檢查service。詳情參看Section 17.2, “The DispatcherServlet”

@Required

`@Required`注解應用於bean的setter方法,像這樣: ```java public class SimpleMovieLister {

private MovieFinder movieFinder;

@Required
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// ...

}


這個注解意思是受到影響的bean屬性在配置時必須賦值,在bean定義中明確指定其屬性值或者通過自動注入。若該屬性未指定值,容器會拋異常。這導致及時明確的失敗,避免`NullPointerExceptions`或者晚一些時候才發現。仍然推薦,你在編碼過程中使用斷言,舉個栗子,在`init`方法,做了這些強制的必須引用的檢查,但是屬性值甚至不再容器范圍內。

<h4 id='beans-autowired-annotation'>@Autowired</h4>
如你所料,`@Autowired`注解也是應用在"傳統的"setter方法上:
```java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

注意

注意 在下面的樣例中,使用JSR 330的@Inject注解可以替代@autowired注解。詳情參看這里

也可以將注解用於帶一個或多個參數的其他方法上,看樣例:

public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }

@Autowired也可以應用於構造函數上或者屬性上:

public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }

也可以用在數組上 ,注解標注於屬性或者方法上,數組的類型是ApplicationContext中定義的bean的類型。

public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ... }

同樣也可以應用於集合

public class MovieRecommender { private Set<MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }

注意

注意 如果你想指定數組元素或者集合元素的次序,那么可以通過以下方式:bean實現org.springframework.core.Ordered接口,或者使用Order或者使用@priority注解。

甚至Map也可以自動注入autowired,只要key的類型是String。 Map的value將會包含期待類型的所有bean,key是相應bean的name:

public class MovieRecommender { private Map<String, MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }

默認情況下,當沒有候選bean可用的時候自動注入會失敗;在方法上、構造函數上、域上的注解,默認是必須的。這個也是可以設置為非必須的,看樣例:

public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired(required=false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }

注意

Only one annotated constructor per-class can be marked as required, but multiple non-required constructors can be annotated. In that case, each is considered among the candidates and Spring uses the greediest constructor whose dependencies can be satisfied, that is the constructor that has the largest number of arguments. 每個類中只有一個被注解的構造函數能被標記為required,但是其他構造函數也能被注解。這種情況下,每個構造函數都會在候選者之間被考慮,Spring使用貪婪模式選取構造函數,也就是擁有最多數量構造參數的那一個。TODO 推薦使用@Autowiredrequired屬性覆蓋@Required注解。required屬性表名那個屬性對於自動注入可能不需要,如果不能被裝配則屬性將會被忽略。Required,另一方面,更加強調的是,強制執行設置屬性值,可以通過容器提供的任何手段。

可以使用@Autowired注入常見的Spring API的依賴:BeanFactory,ApplicationContext,Environment,ResourceLoader,ApplicationEventPublisher,MessageSource。這些接口和他們的子類及實現類都可以比如,ConfigurableApplicationContext,ResourcePatternResolver都可以自動解析,無需特別設置。

public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... }

注意

@Autowired, @Inject, @Resource, and @Value注解由BeanPostProcessor接口的實現類處理,也就是說你不能使用自定義的BeanPostProcessor或者自定義BeanFactoryPostProcessor應用這些注解。這些類型的組裝,必須明確的由XML或者使用Spring @Bean方法完成。

使用限定符對自動注入注解微調

因為根據類型自動注入會導致多個候選者,對於選擇哪個候選者則需要更細粒的控制。其中一種方式是使用Spring的`@Qualifier`注解。可以將`qualifier`關聯指定參數,讓類型匹配自動注入可精准的選擇所需的bean。看樣例: ```java public class MovieRecommender {

@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;

// ...

}


`@Qualifier`注解也可以在構造參數或者方法參數上使用:
```java
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;
    }

    // ...

}

下面看看相應的bean定義,也就是上例中Qualifier("main")的bean是如何定義的,也就是如何制定bean的Qualifier

<?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的name會作為備用qualifier值。因此你可以定義bean的id為main替代內嵌的qualifier元素,這將同樣會匹配上。然而,雖然可以使用此慣例通過name去引用,但是@Autowired是基於類型驅動注入,qualifiers只是可選項。這就意味着qualifier值,甚至是bean 的name作為備選項,只是為了縮小類型匹配的范圍;他們並不能作為引用的bean的唯一標示符。好的qualifier值是main,EMEA,persistent,能表達具體的組件的特性,這些qualifier獨立於bean的id,因為id可能是匿名bean自動生成的。

限定符Qualifiers也能應用於集合,就像上面討論的那樣,舉個栗子,設置Set<MovieCtalog>。這個場景中,根據聲明的限定符qualifiers所匹配的bean都會被注入到集合內。這意味着限定符qualifiers並不是唯一的;它們更像是簡單的分類。比如,定義多個beanMovieCatalog,使用相同的限定符qulifier值action;這些bean都將被注入到帶有@Qualifier("action")注解的Set<MovieCatalog>中。

注意

若要通過name名字來驅動注解注入,首先不能使用@Autowired,甚至不能使用@Qualifier來表示引用bean。而是,使用JSR-250的Resource注解,該注解能在語法上標識出目標組件的唯一標識,匹配時將會忽略聲明bean的類型。

使用此語法的導致了這樣的結果,包含某類型bean的集合或者map不能通過@Autowired注解注入,因為@Resouce並不是使用類型匹配的。這些bean使用@Resource,將會通過唯一的name引用指定的集合或者map。@Autowired應用於域field,構造函數,和多參數方法,允許在參數上使用qualifier限定符注解縮小取值范圍。作為對比,@Resouce僅支持域field和bean屬性的setter方法,該方法只能有一個參數。因此,如果你注入的目標是構造函數或者是多參數方法,你得使用qualifiers限定符。

You can create your own custom qualifier annotations. Simply define an annotation and provide the @Qualifier annotation within your definition: 你可以創建自定義的限定符qualifier注解。定義一個簡單的的注解,在定義上提供一個@Qulifier注解:

@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); }

這是,你就能在自動裝配域和參數上使用自定義qualifier限定符:

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/>標簽上增加<qualifier/>標簽子元素,然后指定type類型和value值來匹配自定義的qualifier注解。type是自定義注解的權限定類名(包路徑+類名)。如果沒有重名的注解,那么可以使用類名(不含包路徑)。看樣例:

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

Section 5.10, “Classpath scanning and managed components”中,你會看到基於注解的XMLqualifier限定名元數據。詳細的信息參看Section 5.10.8, “Providing qualifier metadata with annotations”.

在某些場景中,若不給注解指定value值的話可能不能滿足需求。出於范型目的的注解,可以應用到不同類型的依賴。比如,你可以提供一個離線目錄在無網絡連接時候任然可以檢索。先定義一個簡單的注解:

@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }

然后在自動裝配的field域或者propety屬性上增加該注解:

public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... }

現在,bean的定義只需要指定限定qualifier類型:

<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!-- inject any dependencies required by this bean --> </bean>

也可以為自定義限定名qualifier注解增加屬性,用於替代簡單的value屬性。如果有多個屬性值在自動裝配的域field或者是參數上指定,bean的定義必須全部匹配這些屬性值才能作為自動裝配的候選者。舉個栗子,看樣例代碼:

@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); }

Format是個枚舉enum:

public enum Format { VHS, DVD, BLURAY }

需要自動裝配的域field都是用自定義qulifier注解,注解中都設置了2個屬性: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; // ... }

最后,Spring bean的定義應該包含匹配qualifier的values值。這個例子中展示了beanmeta屬性替代的<qualifier/>子元素。如果可以,<qualifier/>及其屬性優先生效,但是若沒有qualifier出現,自動裝配機制會使用<meta/>標簽的值,看樣例,后面的2個bean定義演示了meta標簽:

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

范型作為自動裝配限定符

`@Qualifier`注解,也常用於java范型作為隱式的限定符qualification。比如:假如有下列配置: ```java @Configuration public class MyConfiguration {

@Bean
public StringStore stringStore() {
    return new StringStore();
}

@Bean
public IntegerStore integerStore() {
    return new IntegerStore();
}

}


假設上面的bean實現了范型接口,也就是`Store<String>`和`Store<Integer>`,那么久可以使用`@Autowired`注解`Store`接口,范型作為限定符qualifier:
```java
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

范型限定符qualifiers也同樣應用於自動裝配Lists,MapsArrays:

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

CustomAutowireConfigurer

`CustomAutowireConfigurer`是`BeanFactoryPostProcessor`,允許注冊自定義限定符qualifier注解類型,無需指定`@Qualifier`注解: ```xml example.CustomQualifier ``` `AutowireCandidateResolver`是自動裝配候選者而定: * 每一個bean定義中的`autowire-candidate`值 * ``元素上`default-autowire-candidates`可用的模式 * the presence of @Qualifier annotations and any custom annotations registered with the CustomAutowireConfigurer * 出現的`@Qualifier`注解和任何通過`CustomAutowireConfigurer`注冊的自定義注解。

當多個bean的qualify限定符作為自動裝配的候選者,“首要bean”決定於:候選者中有bean指定了primary屬性值為true,那么它將陪選中(注入)。

@Resouce

Spring也支持JSR-250`@Resource`注解注入,標注在域field或者屬性setter方法上。這是 Java EE 5 and 6中常用的模式,比如JSF1.2管理的bean或者JAX-WS2.0的endpoints。 Spring管理Spring對象支持這些模式。

@Resource使用name屬性,Spring默認解釋其value值作為注入bean的名字。看樣例:

public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }

如果沒有明確的指定name ,默認的name取值就是域field name或者從setter方法派生。如果是域name,則原封不動的使用該域;如果是setter方法派生,將獲取setter方法內設置的屬性property名字(譯注,應該就是field name域名字)。所以下面的樣例中,會吧bean名字為"movieFinder"注入給setter方法:

public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }

注意

ApplicationContext若使用了CommonAnnotationBeanPostProcessor,注解提供的name名字將被解析為bean的name名字。如果配置了明確的Spring的SimpleJndiBeanFactory,這些name名字將通過JNDI解析。然而,推薦你使用默認的行為,簡單的使用Spring的JNDI,這樣可以保持邏輯引用,而不是直接引用。

@Resource沒有明確指定name時,和@Autowired相似,對於特定bean(SpringAPI內的bean),@Resource會以類型匹配方式替代bean name名字匹配方式,比如:BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, and MessageSource 接口

因此,下面的樣例中,customerPreferenceDaofield域首先查找名字為customerPreferenceDao的bean,若未找到,則會使用類型匹配CustomerPreferenceDao類的實例。contextfield域將會注入ApplicationContext

public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; public MovieRecommender() { } // ... }

@PostConstruct and @PreDestroy

`CommonAnnotationBeanPostProcessor`不僅能識別`@Resource`注解,也能識別JSR-250生命周期注解。Spring 2.5提供了對這些注解的支持,也提供了以下注解的支持:[initialization callbacks](#beans-factory-lifecycle-initializingbean) and [destruction callbacks](#beans-factory-lifecycle-disposablebean)。`CommonAnnotationBeanPostProcessor `提供了這些注解的支持,它是Spring `ApplicationContext`注冊,它會在相應的Spring bean生命周期調用相應的方法,就像是Spring生命周期接口方法,或者是明確聲明的回調函數。在下面的樣例中,會根據初始化方法執行緩存,在銷毀時執行清理。 ```java public class CachingMovieLister {

@PostConstruct
public void populateMovieCache() {
    // populates the movie cache upon initialization...
}

@PreDestroy
public void clearMovieCache() {
    // clears the movie cache upon destruction...
}

}

![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png)  
> 關於多重生命周期變量機制,詳情參看[the section called “Combining lifecycle mechanisms”](#beans-factory-lifecycle-combined-effects)

<h3 id='beans-classpath-scanning'>ClassPath掃描和管理組件</h3>
本章大多數的樣例都是使用XML格式配置元數據配置Spring bean,然后Spring容器根據這些配置產生`BeanDefinition`。雖然前面章節([Section 5.9, “Annotation-based container configuration”](#beans-annotation-config))展示了大量的源碼級別注解配置元數據的情況,但是,注解也僅僅用於驅動依賴注入,"base"bean依然是在明確的在XML文件中定義。本章將講解新的內容,如何通過掃描classpath ,隱式檢索特殊的Spring bean,也就是后面提到的候選者組件。候選者組件是class類,這些類經過過濾匹配,由Spring容器注冊注冊的bean定義,成為Spring bean。這樣就沒有XML什么事兒了,*譯注go egg,太粗魯了吧*,也就是無需使用XML定義bean,而是使用注解(比如`@Component`),`AspectJ`表達式,或者是自定義的過濾器,使用自定義的過濾器選擇那些類將會被注冊到容器中。
![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png)  
> Spring3.0開始,`JavaConfig`項目中很多功能已經集成到Spring框架中。這就可以使用Java定義beans而不是傳統的XML了。看看`@Configuration`,`@Bean`,`@Import`,`@DependsOn`的樣例,即可學習如何使用這些功能。

<h4 id='beans-stereotype-annotations'>@Component和各代碼層注解</h4>
`@Repository`注解注解用於DAO層。使用此注解會自動轉換異常,詳情參看[Section 15.2.2, “Exception translation”](#orm-exception-translation)

Spring提供了各層代碼注解:`@Component, @Service, and @Controller`。`@Component`是通用的Spring bean,也即是由Spring管理的組件。`@Repository, @Service, @Controller`和`@Component`相比,更加精准的用於各個代碼層,它們分別用於持久化層persistence,service服務層,和presentation layers表現層。因此,可以將類注解`@Component`,但是如果使用` @Repository, @Service, or @Controller`替代,也許更適於工具去處理,或者和`aspects`關聯。比如,在某層代碼上做切點。也許在Spring框架未來的版本中,` @Repository, @Service, and @Controller `會附加更多的功能,也就是易於擴展。因此,對於在service層使用`@Component`還是`@Service `的糾結,無疑`@Service`是最好的選擇。同理,在持久化層要選擇`@Repository`,它能自動轉換異常。

<h4 id='beans-meta-annotations'>Meta-annotations元注解</h4>
*譯注,元注解就是修飾注解的注解*
Spring提供的很多注解能作為“元注解”使用。元注解是簡單的注解,可以應用於其他注解。比如,前面提及的`@Service`注解就是`@Component`的元注解。
```java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....

}

多個元注解也能聯合起來,成為復合注解。比如,Spring MVC中的@RestController注解就是有@Controller@ResponseBody。 With the exception of value(), meta-annotated types may redeclare attributes from the source annotation to allow user customization. This can be particularly useful when you want to only expose a subset of the source annotation attributes. For example, here is a custom @Scope annotation that defines session scope, but still allows customization of the proxyMode. 對於value()的異常,元注解也許會重新定義源碼注解中的屬性。當僅需要暴露源碼注解子集注解屬性時,這非常有用。比如,現有自定義@Scope注解定義了session 作用域,但是仍然允許代理模式的自定義。TODO

@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Scope("session") public @interface SessionScope { ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT }

自動探測類和自動注冊bean定義

Spring能自定探測各代碼層的類並在`ApplicationContext`內注冊相應的`BeanDefinitions`。比如:下面兩個類就可以被自動探測 ```java @Service public class SimpleMovieLister {

private MovieFinder movieFinder;

@Autowired
public SimpleMovieLister(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

}

blablabal
```java
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要想自動探測這些類並注冊相應的Spring bean,得在@Configuration注解的類上增加@ComponentScan,其basePackages屬性就是上面兩個類的所在的父級包路徑。(或者,可以使用兩個類各自所在的包路徑,用 ,逗號/;分號/ 空格分隔的)

@Configuration
@ComponentScan(basePackages = "org.example") public class AppConfig { ... }

注意

為了簡介,上面的也會使用注解的value替代basepackage,也就是ComponentScan("org.example")

下面是XML格式的配置

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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>時,一般不需要<context:annotation-config/>元素

注意

掃描類包需要相應的目錄在classpath內存在。使用Ant構建JAR包時,確保打包任務不要開啟*僅打包文件(無需相應的目錄)*選項。當然了,在某些環境裝因為安全策略,classpath目錄也許不能訪問。比如,基於JDK1.7.0_45或更高版本的JDK的單獨的應用(需要在manifests包清單中設置信任類庫Trusted-Library;詳情參看http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)

此外,當使用component-scan元素時,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都會隱式啟用。意味着這兩個組件也是自動探測和注入的--所有這些都不需要XML配置。

注意

通過設置annotation-config屬性值為false即禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注冊。

使用過濾器自定義掃描

默認情況下,使用注解`@Component, @Repository, @Service, @Controller`以及基於`@Component`的元注解的類是唯一的被掃描的目標。然而,可以通過自定義過濾器輕易修改、擴展此行為。設置`@ComponentScan`注解的*includeFilters* 和*excludeFilters*參數(或者是XML中,設置`component-scan`元素的子元素include-filter or exclude-filter)。每個過濾器元素需要設置`type`和`expression`屬性。下面的列表中描述的過濾器的選項:

Table 5.5. Filter Types

Filter Type Example Expression Description
annotation (default) org.example.SomeAnnotation 目標組件上出現類注解
assignable org.example.SomeClass 指定類或者接口
aspectj org.example..*Service+ AspectJ 類型表達式匹配目標組件
regex org\.example\.Default.* 正則表達式匹配目標組件的類名
custom org.example.MyTypeFilter 自定義的org.springframework.core.type .TypeFilter接口實現

下例展示了如何忽略所有@Repository注解的類,而僅使用包含字串"stub"的repositories

@Configuration
@ComponentScan(basePackages = "org.example", includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), excludeFilters = @Filter(Repository.class)) public class AppConfig { ... }

和下面的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>

注意

可以關閉默認的過濾器,通過在注解上設置useDefaultFilters=false,或者在<component-scan/>元素上設置use-default-filters="false"屬性。這將會關閉@Component, @Repository, @Service, or @Controller自動探測類注解。

在組件內定義Spring bean

Spring組件也能為容器定義bean定義元數據。在`@Configuration`注解的類中使用`@Bean`注解定義bean元數據(也就是Spring bean)。看樣例: ```java @Component public class FactoryMethodComponent {

@Bean
@Qualifier("public")
public TestBean publicInstance() {
    return new TestBean("publicInstance");
}

public void doWork() {
    // Component method implementation omitted
}

}

*譯注,上面樣例是SPring參考手冊中給出的源碼,待驗證@Component類中使用@Bean?不是@Configuration么TODO*

這個類是一個Spring組件,有個方法`doWork()`。然而,它還有一個工廠方法`publicInstance()`,用於產生一個bean定義。`@Bean`注解了工廠方法,還設置了其他的bean定義的屬性,比如使用`@Qulifier`設置了其標示符的值。此外還支持一些方法級別注解,`@Scope,@Lazy,`或者是自定義的qualifier 注解。

![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png)  
> 除了它本身作為組件初始化的角色,`@Lazy`注解也可以在`@Autowired`或者`@Inject`處使用。這種情況下,該注入將會變成延遲注入代理lazy-resolution proxy

前面討論過的,`@Bean`注解的方法支持自動注入域和自動:
```java
@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
    @Scope(BeanDefinition.SCOPE_SINGLETON)
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }

}

樣例中,自動注入的方法參數,類型String,名稱為country,將會被設置為另一個實例privateInstanceAge屬性。Spring EL表達式語言通過#{<expression>}定義屬性值。對於@Value注解,表達式解析器在解析表達式后,會查找bean的名字並設置value。

在Spring component中處理@Bean和在@Configuration中處理是不一樣的。區別在於,在@Component中,不會使用CGLIB增強去攔截方法和屬性的調用。在@Configuration注解的類中,@Bean注解的方法創建的bean對象的方法和屬性的調用,是使用CGLIB代理。方法的調用不是常規的java語法。作為對比,@Component類中的對於@Bean注解的方法或者屬性的調用,是標准的java語法。//TODO

命名自動注冊組件

掃描處理過程其中一步就是自動探測組件,掃描器使用`BeanNameGenerator`對探測到的組件命名。默認情況下,各代碼層注解(`@Component,@Repository,@Service,@Controller`)所包含的`name`值,將會作為相應的bean定義的名字。

如果這些注解沒有name值,或者是其他一些被探測到的組件(比如使用自定義過濾器探測到的),默認的bean name生成器生成,以小寫類名作為bean名字。比如,下面兩個組件被探測到,bean name將會是myMovieListermovieFinderImpl:

@Service("myMovieLister") public class SimpleMovieLister { // ... }
@Repository
public class MovieFinderImpl implements MovieFinder { // ... }

注意

若你不需要默認的bean命名策略,也可以自己實現命名策略。首先,實現BeanNameGenerator接口,然后確保其包含默認的無參構造函數(即空構造)。然后,在配置掃描器后提供全限定類名:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) public class AppConfig { ... }
<beans>
    <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" /> </beans>

生成規則應當如下,考慮和注解一起生成name,便於其他組件明確的引用。另一方面,當容器負責組裝時,自動生成的名字要能勝任。

為自動探測組件提供作用域

通常來說,Spring管理的組件,默認的最常見的作用域是單例singleton。然而,有時候需要其他的作用域,Spring2.5提供了一個新的注解`@Scope`。只需要給他提供一個name,該注解即可設置作用域: ```java @Scope("prototype") @Repository public class MovieFinderImpl implements MovieFinder { // ... } ``` ![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png) > 若不想使用基於現有注解的方式,而是提供自定義作用域策略,得實現`ScopeMetadataResolver`接口,該實現得有一個空構造(無參構造)。然后,配置掃描時提供該實現類全限定類名:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { ... }
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" /> </beans>

當使用某個非單例作用域時,為作用域對象生成代理也許非常必要。原因參看the section called “Scoped beans as dependencies”component-scan元素中有一個scope-proxy屬性,即可實現此目的。它的值有三個選項:no, interfaces, and targetClass,比如下面的配置會生成標准的JDK動態代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) public class AppConfig { ... }
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces" /> </beans>

為注解提供標示符Qualifier

有關`@Qualifier`注解的討論在[in Section 5.9.3, “Fine-tuning annotation-based autowiring with qualifiers”](#beans-autowired-annotation-qualifiers)。那章節中的樣例展示了`@Qualifier`注解和自定義標識符qualifier注解的用法,藉此用來更細粒度的控制自動注入。因為樣例都是基於XML 的bean定義,所以標識符都是在XML中的bean定義上,通過設置`qualifier`或者`meta`子元素在設置的。當使用classpath掃描、自動探測組件時,得在候選者類上使用類注解來提供標識符qualifier元數據。下面的三個樣例展示此技術: ```java @Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... } ```

@Component
@Genre("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
@Component
@Offline public class CachingMovieCatalog implements MovieCatalog { // ... }

注意

如要使用同一個類產生多個bean定義,bean間的區別是qualifier標識符,可以使用XML替代注解定義bean,記住注解元數據是類定義本身的,因此@Qualifier產生的標識符只能屬於一個bean定義,而XML的bean定義中的qualifier標識符才是屬於bean實例的。 就大多數的標注替換而言,元數據和類本身是結合在一起的;而使用xml的時候,允許同一類型的beans在qualifieer元數據中提供變量,因為元數據是依據實例而不是類來提供的。

使用JSR-330標准注解

Spring3.0開始,Spring提供了對JSR-330標准注解(依賴注入)的支持。這些注解以Spring注解相同的方式被掃描。你只需要在classpath中引入相關jar包

注意

若使用Maven,javax.inject artifact三圍坐標在標准maven倉庫中都是可用的(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/),在pom.xml中增加dependency

<dependency>
    <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>

使用@Inject @Name依賴注入

替代`@Autowired`,`@javax.inject.Inject`這樣用: ```java import javax.inject.Inject;

public class SimpleMovieLister {

private MovieFinder movieFinder;

@Inject
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// ...

}


和`@Autowired`一樣,`@Inject`可用於類注解、域注解、方法注解、構造參數注解。如果需要注入指定qualifier標識符的bean,應該使用`@Named`注解,像這樣:
```java
import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

@Named:相當於@Component

使用`@javax.inject.Named`替代`@Component`: ```java import javax.inject.Inject; import javax.inject.Named;

@Named("movieListener") public class SimpleMovieLister {

private MovieFinder movieFinder;

@Inject
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// ...

}


`@Component`通常不指定組件名字。`@Named`也能這么用:
```java
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

使用@Named,也可以Spring注解一樣的使用component-scanning組件掃描

@Configuration
@ComponentScan(basePackages = "org.example") public class AppConfig { ... }

標准注解的限制

使用標准注解時,要知道下列重要功能不可用,這非常重要:

Table 5.6. Spring annotations vs. standard annotations

Spring javax.inject.* javax.inject restrictions / comments
@Autowired @Inject @Inject 沒有required屬性
@Component @Named -
@Scope("singleton") @Singleton JSR-330默認的作用域類似於Spring的prototype原型作用域。為了保持Spring的一致性,在Spring容器中的JSR-330的bean聲明,默認是singleton單例。除了singleton,若要設置作用域,得使用Spring的@Scope注解。javax.inject也提供了一個@Scope注解。然而,這個注解僅僅是為了讓你創建自定義注解用的譯注,也就是元注解的源碼注解?
@Qualifier @Named -
@Value - 無等價注解
@Required - 無等價注解
@Lazy - 無等價注解

基於Java的配置元數據

基本概念@Bean和@Configuration

Spring新功能Java-cofiguration支持`@Configuration`類注解和`@Bean`方法注解

@Bean注解用於表明一個方法將會實例化、配置、初始化一個新對象,該對象由Spring IoC容器管理。大家都熟悉Spring的<beans/>XML配置,@Bean注解方法和它一樣。可以在任何Spring @Component中使用@Bean注解方法,當然了,大多數情況下,@Bean是配合@Configuration使用的。

@Configuration注解的類表明該類的主要目的是作為bean定義的源。此外,@Configuration類允許依賴本類中使用@Bean定義的bean。看樣例:

@Configuration
public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }

上面的AppConfig類等價於下面的XML

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>

@Bean and @Configuration注解下面會深入的探討。首先,以Java-based配置方式用各種方式創建Spring容器

**Full @Configuration vs lite @Beans mode?**
**完全@Configuration模式 VS 簡易@Beans模式**
在非@Configuration 注解的類內使用@Bean的話,Spring容器使用簡易模式處理@Bean。
比如,在`@Component`注解的類內,甚至是`plain old class`內使用`@Bean`都將使用簡易模式。

和完全`@Configuration`模式不同,簡易`@Bean·模式不能容易的使用類內依賴。
一般來說,在簡易模式中,`@Bean`方法不應該引用另一個在方法上注解的`@Bean`

為了確保開啟完全模式,只推薦在`@Configuration`注解的類中使用`@Bean`的手法。

使用AnnotationConfigApplicationContext實例化Spring IoC容器

Spring的`AnnotationConfigApplicationContext`部分,是Spring3.0中新增的。這是一個強大的(*譯注原文中是多才多藝的versatile*)`ApplicationContext`實現,不僅能解析`@Configuration`注解類,也能解析`@Componnet`注解的類和使用`JSR-330`注解的類。

使用@Configuration注解的類作為配置元數據的時候,@Configuration類本身也會注冊為一個bean定義,類內所有的@Bean注解的方法也會注冊為bean定義。

使用@Component和JSR-330注解類作為配置元數據時,他們本身被注冊為bean定義,並假設DI(依賴注入)元數據,像類內使用的@Autowired或者@Inject都是必須的。

簡單結構

Spring以XML作為配置元數據實例化一個`ClassPathXmlApplicationContext`,以`@Configuration`類作為配置元數據時,Spring以差不多的方式,實例化一個`AnnotationConfigApplicationContext`。因此,Spring 容器可以實現零XML配置。 ```java public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); } ```

上述代碼中,AnnotationConfigApplicationContext不是僅能與@Configuration注解類配合使用。任何@Component或者JSR-330注解的類都可以作為其構造函數的參數:

public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }

上述代碼中,假設MyServiceImpl,Dependency1 ,Dependency2使用了Spring依賴注入注解,比如@Autowired

使用 register(Class…)編程式構造Spring容器

`AnnotationConfigApplicationContext`也可以通過無參構造函數實例化,然后調用`registor()`方法配置。此法應用於編程式構造`AnnotationConfigApplicationContext` ```java public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); } ```

開啟組件掃描

要開啟組件掃描,只需要像這樣注解`@Configuration`類: ```java @Configuration @ComponentScan(basePackages = "com.acme") public class AppConfig { ... } ``` ![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png) > Spring老炮兒可能知道它和下面的xml是等同效果的,下面的xml使用了Spring `context`命名空間 > ```xml ```

上面的栗子中,會掃描com.acme package包,檢索出所有@Component-annotated類,Spring容器將會注冊這些類為Spring bean定義。AnnotationConfigApplicationContext暴露的scan(String...)方法也允許掃描類,完成相同的功能:

public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }

注意

記住,@Configuration注解是@Component注解的元注解,所以它是component-scanning的候選者!上面栗子中,假設AppConfigcom.acme package包內生命的(或者是該包路徑下的),在scan()期間,它也會被掃描,在所有@Bean方法處理並且注冊為Spring bean定義之后,它也會注冊到容器中,然后在執行refresh()方法。

譯注,E文看的不大明白,就翻看了源碼

// 先看

org.springframework.context.annotation.AnnotationConfigApplicationContext public AnnotationConfigApplicationContext(String... basePackages) { scan(basePackages); refresh(); }

//接下來看掃描處理

org.springframework.context.annotation.ClassPathBeanDefinitionScanner public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); //掃描 doScan(basePackages); //注冊config // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; }
使用AnnotationConfigWebApplicationContext支持WEB應用

`WebApplicationContext`接口一個實現`AnnotationConfigWebApplicationContext`,是`AnnotationConfigApplicationContext`的一個變體。在配置`ContextLoaderListener`、 Spring MVC `DispatcherServlet`等等時,使用此實現類。下面這段`web.xml`片段,是典型Spring MVC的Web應用的配置。注意`contextClass`類的context-param和init-param。 ```xml contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext

<!-- Configuration locations must consist of one or more comma- or space-delimited
    fully-qualified @Configuration classes. Fully-qualified packages may also be
    specified for component-scanning -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.acme.AppConfig</param-value>
</context-param>

<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </init-param>
    <!-- Again, config locations must consist of one or more comma- or space-delimited
        and fully-qualified @Configuration classes -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.web.MvcConfig</param-value>
    </init-param>
</servlet>

<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/app/*</url-pattern>
</servlet-mapping>

```

使用@Bean注解

`@Bean`是方法注解,和XML中的``元素十分相似。該注解支持``的一些屬性,比如[init-method](#beans-factory-lifecycle-initializingbean), [destroy-method](#beans-factory-lifecycle-disposablebean), [autowiring](#beans-factory-autowire)和`name`

聲明bean

要聲明bean非常簡單,只需要在方法上使用`@Bean`注解。使用此方法,將會在`ApplicationContext`內注冊一個bean,bean的類型是方法的返回值類型。 ```java @Configuration public class AppConfig {

@Bean
public TransferService transferService() {
    return new TransferServiceImpl();
}

}


上面的配置和下面的XML配置等價:
```xml
<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

上面兩種配置,都會在ApplicationContext內產生一個bean定義,名稱為transferService,該Spring bean綁定到一個類型為TransferServiceImpl的實例:

transferService -> com.acme.TransferServiceImpl
接收生命周期回調

使用`@Bean`注解的bean定義,都支持常規生命周期回調,能使用JSR-250中的`@PostConstruct`和`@PreDestroy`注解,[JSR-250詳情請參看這里](#beans-postconstruct-and-predestroy-annotations)

常規Spring生命周期回調也完全支持,若bean實現了InitializingBean, DisposableBean, or Lifecycle,他們各自的方法都會被容器調用。

標准的那一套*Aware接口,像BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等等,也都完全支持。

@Bean注解支持初始化回調和銷毀回調,很像Spring XML中<bean/>元素的init-methoddestroy-method屬性

public class Foo { public void init() { // initialization logic } } public class Bar { public void cleanup() { // destruction logic } } @Configuration public class AppConfig { @Bean(initMethod = "init") public Foo foo() { return new Foo(); } @Bean(destroyMethod = "cleanup") public Bar bar() { return new Bar(); } }

注意

默認情況下,使用java config定義的bean中close方法或者shutdown方法,會作為銷毀回調自動調用。若bean中有close,shutdown方法,又不是銷毀回調,通過設置@Bean(destroyMethod=""),即可關閉該默認的自動匹配銷毀回調模式。 You may want to do that by default for a resource that you acquire via JNDI as its lifecycle is managed outside the application. In particular, make sure to always do it for a DataSource as it is known to be problematic. 對於某些由JNDI獲取的資源,也許就要關閉自動匹配銷毀回調行為了,因為該資源的生命周期並不由應用管理。尤其是,使用DataSource時一定要關閉它,不關會有問題。TODO

@Bean(destroyMethod="") public DataSource dataSource() throws NamingException { return (DataSource) jndiTemplate.lookup("MyDS"); }

Of course, in the case of Foo above, it would be equally as valid to call the init() method directly during construction: 當然了,上面Foo的例子中,也可以在構造函數中調用init()方法,和上面栗子中的效果相同

@Configuration
public class AppConfig { @Bean public Foo foo() { Foo foo = new Foo(); foo.init(); return foo; } // ... }

注意

如果是直接使用java,對於對象,你想怎么搞就怎么搞,並不總需要依賴容器生命周期

指定bean作用域scope
使用@Scope注解

通過`@Bean`注解定義的bean也許需要指定作用域。可以使用[bean作用域](#beans-factory-scopes)章節中的任意標准作用域。 默認的作用域是`singleton`單例,但是可以使用`@Scope`注解覆蓋此設置: ```java @Configuration public class MyConfiguration {

@Bean
@Scope("prototype")
public Encryptor encryptor() {
    // ...
}

}


<h6 id='#beans-java-scoped-proxy'>@Scope 和作用域代理</h6>
Spring提供了非常方便的方式,通過[scoped proxies](#beans-factory-scopes-other-injection)作用域代理完成作用域bean依賴。若使用XML配置,最簡單的方式是使用`<aop:scoped-proxy/>`元素創建一個代理。若是在Java代碼中配置bean,有一種等價的做法,使用`@Scope`注解並配置其`proxyMOde`屬性.默認配置是沒有代理`ScopedProxyMode.NO`,但是你可以設置`ScopedProxyMode.TARGET_CLASS`或者`ScopedProxyMode.INTERFACES`。
如果將XML格式的作用域代理示例轉換成Java中使用`@Bean`,差不多是這樣:
```java
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
自定義bean名字

默認情況下,配置類中,使用`@Bean`的方法名作為返回bean的名字。通過配置可以覆蓋此設置,使用`name`屬性 即可。 ```java @Configuration public class AppConfig {

@Bean(name = "myFoo")
public Foo foo() {
    return new Foo();
}

}


<h5 id='beans-java-bean-aliasing'>bean別名</h5>
在之前討論過的bean別名[Section 5.3.1, “Naming beans”](#beans-beanname),有時候需要給一個bean指定多個name。`@Bean`注解的`name`屬性就是干這個用,該屬性接收一個字串數組。
```java
@Configuration
public class AppConfig {

    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }

}
bean描述

有時候,給bean提供一個更具細節的描述,是非常有好處的。用於監視目的(通過JMX)的時候,非常有用。 給一個`@Bean`增加描述,可使用`@Description`注解: ```java @Configuration public class AppConfig {

@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
    return new Foo();
}

}


<h4 id='beans-java-configuration-annotation'>使用@Configuration注解</h4>
`@Configuration`是類注解,表名該類將作為bean定義的配置元數據。`@Configuration`類內只用`@Bean`注解方法。在`@Configuration`類上調用`@Bean`方法,也能用於內部bean依賴。詳情參看[Section 5.12.1, “Basic concepts: @Bean and @Configuration”](#beans-java-basic-concepts)


<h5 id='beans-java-injecting-dependencies'>注入內部bean依賴</h5>
當`@Beans`依賴其他bean,依賴的表達式非常簡單,僅需要調用被依賴的bean的方法:
```java
@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }

}

在上面的樣例中,foo bean接收一個參數,該參數是通過構造返回的實例bar,以此完成注入。

注意

聲明內部bean依賴的做法,僅在@Configuration類內的@Bean注解的方法上有效。@Component類不能使用此做法。

查找方法注入

早先提到過,方法注入[lookup method injection](#beans-factory-method-injection) 是一個高級功能,很少會用到。但是,在一個單例bean依賴原型作用域bean的場景中,就非常有用了。Java中,提供了很友好的api實現此模式。 ```java 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();

}

使用基於java的元注解配置,可以創建一個`CommandManager`的子類,子類重寫父類抽象 方法,該方法返回一個`new`創建的對象。
```java
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
基於java配置的內部工作原理

下面示例中,展示了`@Bean`注解的方法被調用了2次: ```java @Configuration public class AppConfig {

@Bean
public ClientService clientService1() {
    ClientServiceImpl clientService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientService;
}

@Bean
public ClientService clientService2() {
    ClientServiceImpl clientService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientService;
}

@Bean
public ClientDao clientDao() {
    return new ClientDaoImpl();
}

}


`clientDao()`被`clientService1()`調用了一次,被`clientService2()`調用了一次。因為這個方法會創建一個`ClientDaoImpl`類的實例並返回,也許你以為會有2個實例(分別返回給各個service)。這個定義會有問題:在Spring中,實例化bean默認的作用域是單例。這就是它的神奇之處:所有的`@Configuration`類在啟動時,都是通過`CGLIB`創建一個子類。在調用父類的方法並創建一個新的實例之前,子類中的方法首先檢查是否緩存過。注意,自Spring3.2其,不在需要將`CGLIB`加入到classpath中,因為`CGLIB`包已經被打包進`org.springframework`下,在Spring核心包中已經內置了。


![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png)  
> 該行為也許有些不同,這得根據具體的bean的作用域。這里討論的是singleton單例作用域。

![注意](http://docs.spring.io/spring/docs/4.2.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/images/note.png)  
> 因為一些限制,導致`CLGIB`會在啟動時動態增加功能
> * **Configuration**配置類不能是`final`
> * 他們應該有空構造

<h4 id='beans-java-composing-configuration-classes'>組裝java配置元數據</h4>
<h5 id='beans-java-using-import'>使用@Import注解</h5>
在Spring XML配置中使用`<import/>`元素,意在模塊化配置,`@Import`注解也允許從其他配置類中加載`@Bean`定義。
```java
@Configuration
public class ConfigA {

     @Bean
    public A a() {
        return new A();
    }

}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }

}

現在,實例化context時,不需要同時指定ConfigA.classConfigB.class,而是僅需要提供ConfigB即可:

public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }

該方式簡化了容器實例化,只需要一個類去處理,而不是需要開發者在構造期間記着大量的@Configuration

在導入的bean上注入依賴

上面的栗子可以運行,但是太簡單了。在大部分實際場景中,bean都會跨配置依賴。若使用XML,這不是問題,因為不包含編譯器,開發者簡單的聲明`ref=somBean`並相信Spring在容器實例化期間會正常運行。但是,使用`@Configuration`類,配置模型替換為java編譯器,為了引用另一個bean,Java編譯器會校驗該引用必須是有效的合法Java語法。

非常幸運,解決這個這個問題非常簡單。還記得不,@Configuration類在容器中本身就是一個bean,這意味着他們能使用高級@Autowired注入元數據,就像其他bean一樣。

來一個更加真實的場景,使用了多個@Configuration類,每個配置都依賴其他配置中是bean聲明:

@Configuration
public class ServiceConfig { @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public DataSource dataSource() { // return new DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes... TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }

在上面的場景中,@Autowired可以很好的工作,使設計更具模塊化,但是自動注入的是哪個bean依然有些模糊不清。舉個栗子,開發者正在查找ServiceConfig,你怎么知道一定會有@Autowired注解的AccountRepository類型bean聲明?代碼中並未明確指出,還好,Spring Tool Suite提供了可視化工具,用來展示bean之間是如何裝配的,也許這就是你需要的。另外,你的Java IDE也很容易找到所有的有關AccountReository類型的聲明和調用,並很快的定位返回該類型的@Bean方法。

萬一需求不允許這種模糊的裝配,並且你要在IDE內從Configuration類直接定位到依賴類bean,考慮使用硬編碼,即由依賴類本身定位:

@Configuration
public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { // navigate 'through' the config class to the @Bean method! return new TransferServiceImpl(repositoryConfig.accountRepository()); } }

上栗中,AccountRepository已經定義好了,它非常明確。然而,ServiceConfigRepositoryConfig已經緊耦合在一起了;這是一個折中的方案。可以通過面向接口或者抽象類解耦。看這段:

@Configuration
public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { @Bean public DataSource dataSource() { // return DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }

現在ServiceConfig已經和具體實現類DefaultRepositoryConfig解耦了,IDE內置的工具此時也很有用:它能很容器的獲取RepositoryConfig實現的集成層級。與常規的面向接口的代碼定位相比,用這種方式,導航@Configuration類和它們的依賴將毫無困難。

過濾@Configuration或者@Bean

在某些需求下,開啟或者關閉一個`@Configuration`類,甚至是針對個別`@Bean`方法開啟或者關閉,通常很有用。Spring環境中,通常使用`@Profile`注解,在某種條件下來激活bean,來實現此效果([see Section 5.13.1, “Bean definition profiles”](#beans-definition-profiles) for details)。

@Profile注解實現了更具彈性@Conditional注解。@Conditional注解表名,指定的org.springframework.context.annotation.Condition實現必須在@Bean注冊之前運行。

Condition接口的實現只需要實現一個簡單的方法, matches(...),該方法返回true或者false。舉個栗子,下面是Condition的實現,用於@Profile

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { // Read the @Profile annotation attributes MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true; } } return false; } } return true; }

See the @Conditional javadocs for more detail.

混合java和xml配置

Spring的`@Configuration`類並非是為了完全替換Spring XML。有些工具,比如XML命名空間就是一種理想的配置方式。如果XML更方便或是必須的,你就得選擇:或者選擇基於XML的配置方式實例化容器,比如使用`ClassPathXmlApplicationContext`,或者選擇基於Java配置風格使用`AnnotationConfigApplcationContext`加上`@ImportResource`注解導入必須的XML。

基於XML混合使用@Configuration類

ad-hoc風格也許是稍好的以XML引導Spring容器,並引入`@Configuration`類。舉個栗子,已存在大量的使用了SPringXML的代碼,有需求需要使用`@Configuration`類,這些配置類需要引入到現存的XML文件中,此種做法也許更容易。接下來看看此場景。

@Configuration類本身在容器內就是一個bean。下面的樣例中,創建了一個@Configuration類,類名是AppConfig,引入一個配置文件system-test-config.xml。由於<context:annotation-config/>打開,容器會識別@Configuration注解,並處理AppConfig類內聲明的@Bean注解的方法。

@Configuration
public class AppConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public TransferService transferService() { return new TransferService(accountRepository()); } }
system-test-config.xml
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>

jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=

public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }

注意

system.test-config.xml中,AppConfig<bean/>沒有id屬性,因為沒有其他bean引用,也不會根據name從容器獲取,所以id不是必須指定的,同樣,DataSourcebean,它只會根據類型自動裝配,所以明確的id也不是必須的。

因為@Configuration@Component的元數據注解,@Configuration注解類也會自動作為掃描組件的候選者。還是上面的場景,我們能重新定義system-test-config.xml,使之能啟用高級掃描組件。注意,在此場景中,我們不需要明確的聲明<context:annotation-config/>,因為<context:component-scan/>會開啟所有相同的功能。

system-test-config.xml
<beans>
    <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
基於@Configuration混合使用xml配置

在應用中,`@Configuration`類是主要的容器配置機制,但是仍然可能會需要一些XML。在這些場景中,使用`@ImportResource`,即可引用XML配置。這樣配置可是實現此效果,基於java配置,盡可能少的使用XML。 ```java @Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig {

@Value("${jdbc.url}")
private String url;

@Value("${jdbc.username}")
private String username;

@Value("${jdbc.password}")
private String password;

@Bean
public DataSource dataSource() {
    return new DriverManagerDataSource(url, username, password);
}

}


```xml
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=

public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }

環境抽象

`Environment`環境在容器中是一個抽象的集合,是指應用環境的2個方面: [profiles](#beans-definition-profiles) 和 [properties](#beans-property-source-abstraction).

profile配置是一個被命名的,bean定義的邏輯組,這些bean只有在給定的profile配置激活時才會注冊到容器。不管是XML還是注解,Beans都有可能指派給profile配置。Environment環境對象的作用,對於profiles配置來說,它能決定當前激活的是哪個profile配置,和哪個profile是默認。

在所有的應用中,Properties屬性扮演一個非常重要的角色,可能來源於一下源碼變量:properties文件,JVM properties,system環境變量,JNDI,servlet servlet context parameters上下文參數,專門的Properties對象,Maps等等。Environment對象的作用,對於properties來說,是提供給用戶方便的服務接口,方便撰寫配置、方便解析配置。

bean定義profiles

bean定義profiles是核心容器內的一種機制,該機制能在不同環境中注冊不同的bean。環境的意思是,為不同的用戶做不同的事兒,該功能在很多場景中都非常有用,包括: * 開發期使用內存數據源,在QA或者產品上則使用來自JNDI的相同的數據源 * 開發期使用監控組件,當部署以后則關閉監控組件,是應用更高效 * 為用戶各自注冊自定義bean實現

考慮一個實際應用中的場景,現在需要一個DataSource。開測試環境中,這樣配置:

@Bean
public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("my-schema.sql") .addScript("my-test-data.sql") .build(); }

現在想一想,如何將應用部署到QA或者生產環境,假設生產環境中使用的JNDI。我們的dataSource bean看起來像這樣:

@Bean(destroyMethod="") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }

問題是,在當前環境中如何切換這兩個配置。隨着時間推移,Spring用戶設計了很多種方式完成此切換,通常使用系統環境變量和XML<import/>綁定,<import/>元素包含一個${placeholder}符號,使用環境變量來設置${placeholder}符號所代表的值,從而達到切換正確配置文件的目的。bean定義profiles是核心容器功能,提供針對子問題的解決方案。

概括一下上面的場景:環境決定bean定義,最后發現,我們需要在某些上下文環境中使用某些bean,在其他環境中則不用這些bean。你也許會說,你需要在場景A中注冊一組bean定義,在場景B中注冊另外一組。先看看我們如何修改配置來完成此需求。

@Profile

`@Profile`注解的作用,是在一個或者多個指定profiles激活的情況下,注冊某個組件。使用上面的樣例,重寫dataSource配置: ```java @Configuration @Profile("dev") public class StandaloneDataConfig {

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("classpath:com/bank/config/sql/schema.sql")
        .addScript("classpath:com/bank/config/sql/test-data.sql")
        .build();
}

}


```java
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

@Profile可以用於元數據注解,為了組合自定義代碼層注解。下面的的樣例中定義了@Production自定義注解,該注解用於替換@Profile("production"):

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }

@Profile也能注解方法,用於配置一個配置類中的指定bean

@Configuration
public class AppConfig { @Bean @Profile("dev") public DataSource devDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } @Bean @Profile("production") public DataSource productionDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }

注意

如果一個@Configuration類注解了@Profile,類中所有@Bean@Import注解相關的類都將被忽略,除非該profile被激活。如果@Component或者@Configuration注解了@Profile({"p1","p2"}),該類將不會注冊/處理,除非profiles'p1 and/or 'p2'被激活。如果給定的profile,使用了NOT操作(!)前綴,若當前profile未被激活則注解元素將會注冊,等等。對於@Profile({"p1", "!p2"}),在profile 'p1'被激活或者'p2'未激活時,發生注冊。

XML bean定義profile

XML中的`beans`元素有一個`profile`屬性。上面的栗子重寫到2個XML中 ```xml

<jdbc:embedded-database id="dataSource">
    <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
    <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>

```

<beans profile="production" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans>

也可以不用分開2個文件,在同一個XML中配置2個<bean/><bean/>元素也有profile屬性:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- other bean definitions --> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans>

TODO。The spring-bean.xsd has been constrained to allow such elements only as the last ones in the file.它是配置更加靈活,而又不造成XML文件混亂。

開啟profile

要修改配置,我們仍然需要指定要激活哪個文件。如果現在運行上面的樣例應用,它會拋異常`NoSuchBeanDefinitionException`,因為容器找不到`dataSource`bean。

有多種方式激活配置,但是最直接的方式是編程式的方式使用ApplicationContext API:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("dev"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh();

此外,還可以使用spring.profiles.active激活配置,該屬性可以配置在系統環境變量、JVM系統屬性、web.xml中JNDI中的servlet context上下文參數(see Section 5.13.3, “PropertySource Abstraction”)

注意配置文件不是單選;可能會同時激活多個配置文件,編程式的使用方法setActiveProfiles(),該方法接收String...參數,也就是多個配置文件名:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

聲明式的使用spring.profiles.active ,值可以為逗號分隔的配置文件名列表,

-Dspring.profiles.active="profile1,profile2"
默認profile配置

默認的profile配置就是默認開啟的profile配置: ```java @Configuration @Profile("default") public class DefaultDataConfig {

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("classpath:com/bank/config/sql/schema.sql")
        .build();
}

}

如果profile激活了,上面的`dataSource`數據源就被創建了;這就像是提供了默認的bean定義,如果有任何profile配置被激活,默認的的就不在應用了。

默認profile配置文件可以更改,通過環境變量的`setDefaultProfiles`方法,或者是聲明的`spring.profiles.default`屬性值


<h4 id='beans-property-source-abstraction'>PropertySource Abstraction</h4>
Spring的環境抽象提供了用於檢索一系列的property sources屬性配置文件。詳細闡述,參看:
```java
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the ''foo'' property? " + containsFoo);

在上面的片段中,通過較高層次方式檢索SPring是否在當前環境中定義了fooproperty屬性。為了檢索該屬性,環境對象在一組PropertySource對象中執行檢索。PropertySource是key-value鍵值對配置文件的抽象,Spring的StandardEnvironment配置了2個PropertySource對象-其一是JVM系統properties(System.getProperties()),另一個是一組系統環境變量(System.getenv())。

注意

這些默認的property源代表StandardEnvironment,在獨立的應用中使用。StandardEnvironment用默認的property配置源填充,默認配置源包括servlet配置和servlet上下文參數。StandardPortletEnvironment也可以訪問portlet配置和portlet上下文參數。也可以可選的開啟JndiPropertySource,詳情參看Javadoc。

若在系統property中存在foo或者在環境變量中存在foo,當使用StandardEnvironment調用env.containsProperty("foo"),將會返回true

注意

檢索是分層級的。默認情況,系統properties屬性優先於環境變量,索引如果fooproperty這兩兩處都配置了,此時調用env.getProperty("foo"),系統property值將會返回。

最重要的,完整的機制是可配置的。也許你需要一個自定義的properties源,並將該源整合到這個檢索層級中。沒有問題-只需實現和實例化你自定義的PropertySource,並在當前環境中把其加入到PropertySources中:

ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource());

在上面的代碼中,MyPropertySource已經增加到了最高優先級的檢索層級中。如果它有fooproperty屬性,它將會被探測並返回,優先於其他PropertySource中的fooproperty屬性。MutablePropertySourcesAPI暴露了很多方法,允許你精准的操作property屬性源。

@PropertySource

`@PropertySource`注解提供了一個方便的方式,用於增加一個`PropertySource`到Spring的環境中: 給定一個文件"app.properties"包含了key/value鍵值對testbean.name=myTestBean,下面的`@Configuration`類使用了`@PropertySource`,使用這種方式調用`testBean.getName()`將會返回`myTestBean`。 ```java @Configuration @PropertySource("classpath:/com/myco/app.properties") public class AppConfig { @Autowired Environment env;

@Bean
public TestBean testBean() {
    TestBean testBean = new TestBean();
    testBean.setName(env.getProperty("testbean.name"));
    return testBean;
}

}

任何的存在於`@PropertySource`中的`${...}`占位符,將會被解析為定義在環境中的屬性配置文件中的屬性值:
```java
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假設"my.placeholder"代表一個已經注冊的的property屬性,比如,系統屬性或者環境變量,占位符將會被解析為相應的值。如果沒有,那么default/path將會作為默認值。若沒有默認值指定,那么property將不能解析,IllegalArgumentException將會拋出

TOADD

Placeholder resolution in statements

以前,元素中的占位符的值只能解析JVM系統properties或者環境變量。No longer is this the case。因為`Environment`抽象通過容器集成,通過`Environment`可以非常容器的解析占位符。這意味着,你可以你喜歡的方式配置如何解析:可以改變是優先查找系統properties或者是有限查找環境變量,或者刪除它們;增加自定義property源,使之成為更合適的。

下面的自定義property定義,會像Enviroment一樣可用:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/> </beans>

注冊LoadTimeWeaver

`LoadTimeWeaver`用於在JVM加載類時動態轉換。 若要開啟加載時織入,得在`@Configuration`類中增加`@EnableLoadTimeWeaving`: ```java @Configuration @EnableLoadTimeWeaving public class AppConfig {

}

或者在XML中配置,使用`context:load-time-weaver`元素:
```xml
<beans>
    <context:load-time-weaver/>
</beans>

一旦為ApplicationContext做了配置。ApplicationContext內的任何bean都會實現LoadTimeWeaverAware,因此可以接收load-time weaver實例。這種用法和JPA聯合使用非常贊,load-time weaving加載織入對JPA類轉換非常必要。詳情請參看LocalContainerEntityManagerFactoryBean。關於AspectJ load-time weaving更多的詳情,請參看see Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”

補充說明ApplicationContext的能力

就像簡介章節討論的,`org.springframework.beans.factory`包提供了管理和操作bean的基本功能,包含一種編程式的方式。`org.springframework.context`包增加了`ApplicationContext`接口,繼承於`BeanFactory`接口,這也是為了繼承其他的接口,並且提供*應用框架風格*。很多人使用`ApplicationContext`完全是聲明式,甚至不用編程式去創建,依靠像`ContextLoader`這樣的支持類自動實例化`ApplicationContext`,並把此作為Java EE web應用的啟動步驟。

為了增強BeanFactory功能,context 包也支持下列功能:

  • 通過MessageSource接口,i18n-style方式訪問messages。譯注國際化
  • 通過ResourceLoader接口,訪問資源,比如URLS網絡資源定位符和Files文件系統
  • 通過ApplicationEventPublisher,給實現了ApplicationListener接口的bean發布事件,
  • 通過HierarchicalBeanFactory接口,加載多級contexts,允許關注某一層級context,比如應用的web層。

TODO使用MessageSource國際化

`ApplicationContext`接口繼承`MessageSource`接口,因此提供國際化 對這一章暫時不感興趣,不翻了先。

標注事件和自定義事件

`ApplicationContext`通過`ApplicationEvent`類和`ApplicationContext`類提供了事件處理。如果某個bean實現了`ApplicationListener`接口,並注冊在了context中,每當`ApplicationEvent`向`ApplicationContext`發布時間,該bean就會收到通知。其實,這是一個標准的的*觀察者模式*。Spring提供了下列標准事件:

Table 5.7. Built-in Events 事件 | 解釋 ---- | --- ContextRefreshedEvent | 當ApplicationContext初始化或者刷新時發布,比如,使用ConfigurableApplicationContext接口的refresh()方法。這里"初始化"的意思是指,所有的bean已經被加載、post-processor后處理bean已經被探測到並激活,單例bean已經pre-instantiated預先初始化,並且ApplicationContext對象已經可用。只要context上下文未關閉,可以多次觸發刷新動作, 某些ApplicationContext支持"熱"刷新。比如,XmlWebApplicationContext支持熱刷新,GenericApplicationContext就不支持。 ContextStartedEvent | 當ApplicationContext啟動時候發布,使用ConfiruableApplicationContext接口的start()方法。這里的“啟動”意思是指,所有的Lifecycle生命周期bean接收到了明確的啟動信號。通常,這個信號用來在明確的“停止”指令之后重啟beans,不過也可能是使用了啟動組件,該組件並未配置自動啟動,比如:組件在初始化的時候並未啟動。 ContextStoppedEvent | 當ApplicationContext停止時發布,使用ConfigurableApplicationContext接口的stop()方法。這里的“停止”的意思是指所有的Lifecycle生命周期bean接收到了明確的停止信號。一個停止了的context上下文可以通過start()調用來重啟。ContextClosedEvent | 當ApplicationContext關閉時候發布,使用ConfigurableApplicationContext接口的close()方法。這里“關閉”的意思是所有的單例bean已經銷毀。一個關閉的context上下文達到的生命周期的重點。不能刷新,不能重啟。RequestHandledEvent | 是一個web專用事件,告訴所有的beans:一個HTTP request正在處理。這個時間在reqeust處理完成之后發布。該事件僅適用於使用Spring的DispatcherServlet的web應用。

你也可以創建並發布自定義事件。下面樣例展示了這一點,一個簡單的類繼承了Spring的ApplicationEvent類:

public class BlackListEvent extends ApplicationEvent { private final String address; private final String test; public BlackListEvent(Object source, String address, String test) { super(source); this.address = address; this.test = test; } // accessor and other methods... }

為了發布自定義ApplicationEvent,得調用ApplicationEventPublisher接口上的publishEvent()方法。通常是使用一個已經注冊為Spring bean的ApplicationEventPublisherAware接口的實現類來完成的。看樣例:

public class EmailService implements ApplicationEventPublisherAware { private List<String> blackList; private ApplicationEventPublisher publisher; public void setBlackList(List<String> blackList) { this.blackList = blackList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent event = new BlackListEvent(this, address, text); publisher.publishEvent(event); return; } // send email... } }

在配置期間,spring容器發現EmailService實現了ApplicationEventPublisherAware並自動調用setApplicationEventPublisher()方法。實際上,參數就是Spring容器本身;可以通過ApplicationEventPublisher接口和應用上下文簡單的交互。

為了接受自定義ApplicationEvent,得創建一個ApplicationListener的實現類並注冊為spring Bean。看樣例:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } }

注意,ApplicationListener以自定義事件類BlackListEvent作為范型類。也就是說onApplicationEvent()方法是類型安全的,無需向下轉型。event listener事件監聽想注冊多少就注冊多少,但是注意默認情況下,所有的監聽會同步接收到事件。也就是說publishEvent()方法會阻塞所有監聽完成對事件的處理。同步的、單線程的處理的一個優勢是當一個監聽接收到一個事件,若該事件發布者存在可用的事務時,監聽會在發布者事務內操作。如果需要其他的事件發布策略,參看Spring的ApplicationEventMulticastor接口的JavaDoc

下面的樣例展示了之前提到過的類如何定義bean並注冊、配置:

<bean id="emailService" class="example.EmailService"> <property name="blackList"> <list> <value>known.spammer@example.org</value> <value>known.hacker@example.org</value> <value>john.doe@example.org</value> </list> </property> </bean> <bean id="blackListNotifier" class="example.BlackListNotifier"> <property name="notificationAddress" value="blacklist@example.org"/> </bean>

將他們組裝到一起,當emailServicebean的sendEmail()方法調用時,如果有email是黑名單中的的,自定義事件BlackListEvent就發布了。balckListNotifierbean注冊成為ApplicationListener,因此可以接收到BlackListEvent,並能通知相關的觀察者。

注意

Spring的事件機制是為了Spirng beans和所在的應用context上下文之間做簡單的交流。然而,為了滿足復雜的企業級集成需求,有個單獨維護的項目Spring Integration project,提供了完整的支持,可用於輕量構建、pattern-oriented,依賴Spring編程模型的事件驅動架構。

便利的訪問底層資源

為了獲得最佳用法和理解應用上下文,推薦大家通過Spring的Resource abstraction資源抽象熟悉他們,詳情請參看[Chapter 6, Resources](#resources)

一個應用上下文是一個ResourceLoader,它能加載資源。Resource本質上是JDK的java.net.URL類的擴展,實際上,Resource的實現類中大多含有java.net.URL的實例。Resource幾乎能從任何地方透明的獲取底層資源,可以是classpath類路徑、文件系統、標准的URL資源及變種URL資源。如果資源定位字串是簡單的路徑,沒有任何特殊前綴,就適合於實際應用上下文類型。

可以配置一個bean部署到應用上下文中,用以實現特殊的回調接口,ResouceLoaderAware,它會在初始化期間自動回調。可以暴露Resource的type屬性,這樣就可以訪問靜態資源;靜態資源可以像其他properties那樣被注入Resource。可以使用簡單的字串路徑指定資源,這要依賴於特殊的JavaBean PropertyEditor,該類是通過context自動注冊,當bean部署時候它將轉換資源中的字串為實際的資源對象

The location path or paths supplied to an ApplicationContext constructor are actually resource strings, and in simple form are treated appropriately to the specific context implementation. ClassPathXmlApplicationContext treats a simple location path as a classpath location. You can also use location paths (resource strings) with special prefixes to force loading of definitions from the classpath or a URL, regardless of the actual context type. 定位符是實際的資源字串路徑,作為ApplicationContext構造函數的參數,簡單的形式就能得到特殊的context實現類恰當的處理。ClassPathXamlApplicationContext能解析簡單的定位路徑作為classpath定位,可以使用帶有特殊前綴的定位路徑,這樣就可以強制從classpath或者URL定義加載路徑,無需關注實際的context類型。

譯注,啥玩意,翻的狗屁不通

易用的web應用的`ApplicationContext`實例化

可以通過聲明式方式創建`ApplicationContext`實例,比如,一個`ContextLoader`。當然也可以使用編程時的方式創建`ApplicationContext`實例,得使用`ApplicationContext`實現。 使用`ContexgtLoaderListener`注冊一個`ApplicationContext`,看樣例: ```xml contextConfigLocation /WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml org.springframework.web.context.ContextLoaderListener ```

監聽檢查contextConfigLocation參數。如果參數不存在,監聽默認使用/WEB-INF/applicationContext.xml。如果參數存在,監聽會使用分隔符(逗號,分號,空格)分割參數,應用context會查找這些分割后的定位路徑。Ant風格的路徑支持的非常好。比如,WEB-INF/*Context.xml,將會匹配"WEB-INF"目錄下所有以"Context.xml"結尾的file文件,/WEB-INF/**/*Context.xml,將會匹配"WEB-INF"下所有層級子目錄的Context.xml 結尾的文件。

將Spring ApplicationContext作為JAVA EE RAR文件部署

這章不翻了,沒意思。

BeanFactory

`BeanFactory`為Spring的IoC功能提供了基礎支撐,但是在集成第三方框架他只能直接使用,現在已經成為歷史。`BeanFactory`和相關接口,比如`BeanFactorAware,InitializingBean,DisposableBean`,依然存在,是為了大量的集成了spirng的第三方框架向后兼容。常常有第三方組件不能使用現代風格的SPring,比如`@PostConstruct`或者`@PreDestroy`,是為了保留JDK1.4的兼容性,或者是為了避免依賴`JSR-250`。 本部分主要講解有關`BeanFactory`和`ApplicationContext`之間的不同的背景,通過一個經典的檢索單例類闡述他們如何直接的訪問IoC容器。

BeanFactory or ApplicationContext

優先使用`ApplicationContext`,除非你有非常好的理由不用它。 因為`ApplicationContext `包含了`BeanFactory`所有的方法,和`BeanFactory`相比更值得推薦,除了一些特定的場景,比如,在資源受限的設備上運行的內嵌的應用,這些設備非常關注內存消耗。無論如何,對於大多數的企業級應用和系統,`ApplicationContext`都是首選。Spring使用了的大量的`BeanPostProcess`[擴展點](#beans-factory-extension-bpp),如果使用簡單的`BeanFactory`,大量的功能將失效,比如:transactions 和AOP ,至少得多一些額外的處理。它會造成困擾,因為配置中所有的都不錯。

下面的表格中列舉了BeanFactoryApplicationContext提供的功能: Table 5.8. Feature Matrix

功能 BeanFactory ApplicationContext
bean實例化和組裝 YES YES
自動注冊BeanPostProcessor NO YES
自動注冊BeanFactoryPostProcessor NO YES
便利的消息資源訪問(用於i18n) NO YES
ApplicationEvent 發布 NO YES

為了注冊一個bean post-processor 給BeanFactory,得這么干:

ConfigurableBeanFactory factory = new XmlBeanFactory(...); // now register any needed BeanPostProcessor instances MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // now start using the factory

在使用Beanfactory時,注冊BeanFactoryPostProcessor,得這樣:

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); // bring in some property values from a Properties file PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory);

上面2個栗子,顯示注冊步驟非常不便,這就是為什么推薦使用ApplicationContext的原因之一,就是為了方便的使用BeanFactoryPostProcessorsBeanPostProcessors。這些機制實現了一些非常重要的功能,比如property placeholder replacement and AOP

耦合代碼和邪惡的單例

在應用中,推薦使用依賴注入的方式編寫,使用Spring IoC容器管理的代碼,當需要創建實例時,從容器獲取他的依賴關系,並且對象對容器將對其一無所知,對於一些小的借合層的代碼,也許會需要與其他層、組件、bean互相協作,可以以單例(准單例)方式使用Spring IoC容器。比如,第三方組件會使用構造器創建新對象(`Class.forName()`風格),而不能使用IoC容器獲取這些對象。 如果第三方組件創建的對象是stub或者proxy代理,然后這些對象以單例風格從Ioc容器獲取真正的對象加以委派,此時控制反轉完成主要工作(對象將脫離於容器管理)。此時,大部分代碼不需知道容器、也不需知道如何訪問容器,好處就是代碼解耦。EJBs也可以使用這種方式,代理的方式委派給簡單的java實現對象,java對象從Ioc容器檢出。然而,spring Ioc容器必須得設計為單例,。。。。。。。//TODO *未翻譯完,翻了完了不通順,第五章總算是搞定了*


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM