Spring系列(零) Spring Framework 文檔中文翻譯

Spring 框架文檔(核心篇1和2)

Version 5.1.3.RELEASE


最新的, 更新的筆記, 支持的版本和其餘主題,獨立的發佈版本等, 是在Github Wiki 項目維護的.html

  • 總覽 歷史, 設計哲學, 反饋, 入門.java

  • 核心 IoC容器, 事件, 資源, 國際化(i18n), 驗證, 數據綁定, 類型轉化, Spring表達式語言(SpEL), 面向切面編程(AOP).mysql

  • 測試 Mock對象, 測試上下文框架(TestContext framework), Spring MVC 測試, WebTestClient.git

  • 數據訪問 事務, DAO支持, JDBC, ORM, 編組XML.github

  • Web Servlet Spring MVC, WebSocket, SockJS, STOMP 消息.web

  • Web Reactive Spring WebFlux, WebClient, WebSocket.正則表達式

  • 集成 Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Cache.算法

  • 語言 Kotlin, Groovy, 動態語言(Dynamic languages).spring


第一部分 總覽

內容:sql

  1. 咱們爲何以"Spring"命名
  2. Spring及Spring框架的歷史
  3. 設計哲學
  4. 反饋和貢獻
  5. 入門

Spring 簡化了Java企業應用的建立. 能夠提供在企業應用環境下Java生態所需的一切, 同時也支持Kotlin和Groovy做爲JVM的替代語言, 根據實際須要,也能夠建立多種不一樣的架構.(architecture). 從Spring Framwork 5.0 開始, Spring須要JDK 8+ 而且已經爲JDK9提供開箱即用支持

Spring提供了普遍的應用場景. 在大型企業應用中,應用每每要存在很長一段時間,而且不得不運行在一個升級週期超出開發人員控制的JDK和服務器上. 而其餘的應用則使用內嵌服務器單獨運行jar包,或者部署在雲環境. 還有一些應用可能獨立部署, 根本不須要服務器(例如批處理或集成負載).

Spring是開源的.背後有長期大量而活躍的根據實際應用案例而提交的反饋.這將幫助Spring成功長期進化.

1. 命名"Spring"的含義

"Spring"意思是在不一樣環境中不一樣的東西. 可以用來指代Spring項目自己(這是它的發端起始點). 隨着時間推移, 其餘創建在Spring之上的項目被建立出來. 一般咱們稱"Spring", 實際上是指全部這些項目. 本文檔主要聚焦基礎: 也就是Spring框架自己.

Spring框架分模塊. 能夠根據狀況選擇須要的模塊. 核心模塊是核心容器, 包含配置模型和依賴注入機制. 還有更多,Spring 框架提供了對不一樣應用架構的基礎支持. 包含消息,事務和持久化,還有Web. 它還包含基於Servlet的MVC框架, 同時提供了對應的交互式框架Web Flux.

關於模塊的提醒: Spring框架jar文件容許JDK9支持的模塊路徑("Jigsaw"). 在此類應用中, Spring Framework 5 的jar文件帶有自動模塊名稱清單. 它定義了獨立於jar工件的語言級別的模塊名稱(「spring.core」,「spring.context」等). 固然, Spring在JDK8和9的類路徑上均可以正常工做.

2. Spring 和 Spring Framework 的歷史

Spring 是爲了迴應早期複雜的J2EE規範於2003年誕生. 有人認爲 Java EE 和 Spring 是競爭關係,實際上,Spring是Java EE 的補充. Spring的編程模型並非徹底擁抱Java EE平臺規範, 而是當心地有選擇地從EE生態中集成了獨立的規範:

  1. Servlet API (JSR 340)

  2. WebSocket API (JSR 356)

  3. Concurrency Utilities (JSR 236)

  4. JSON Binding API (JSR 367)

  5. Bean Validation (JSR 303)

  6. JPA (JSR 338)

  7. JMS (JSR 914)

  8. 若是須要的話,還有 JTA/JCA 作事務協調

Spring Framework 還支持依賴注入(JSR 330)和普通註解(JSR 250)規範. 這些規範的實現由開發人員能夠用來替換Spring默認提供的機制.

Spring Framework 5.0 開始起, Spring要求Java EE 7以上(e.g. Servlet 3.1+, JPA 2.1+).同時使用新的Java EE 8(e.g. Servlet 4.0, JSON Binding API)以上的新api提供開箱即用.這就保證了Spring徹底兼容Tomcat8和9, WebSphere9, 還有JBoss EAP 7.

慢慢的,Java EE 在開發中的角色發生了變化. 早期Java EE 和 Spring建立的程序是被部署到應用程序服務器上. 而今天, 歸功於Spring Boot, 應用程序以devops或雲的方式建立,使用內嵌的Servlet容器, 並且常常變化.自從Spring Framework 5 , WebFlux程序甚至都不須要直接調用Servlet Api了, 它能夠運行在非Servlet規範的容器(如Netty)中.

Spring是持續革新和進化的. 超出Spring Framework, 有不少其餘項目如Spring Boot, Spring Security,Spring Data,Spring Cload, Spring Batch,還有不少. 每一個項目都有它本身的源碼倉庫, 問題跟蹤和發佈週期. 從spring.io/projects 能夠看到全部項目的列表.

3. 設計哲學

當你學習一個框架的時候, 不只要知道它能幹什麼, 更重要的是知道它所遵循的原則. 下面是Spring Framework遵循的指導原則.

  • 在全部層面提供選擇權. Spring容許你儘可能延遲設計選擇. 例如, 你能夠經過配置而不是修改代碼就替換掉數據持久化的提供程序.這也一樣適用於其餘基礎設施概念並能集成不少三方API.

  • 容納不一樣的觀點. Spring 擁抱伸縮性, 它並不堅持認爲事情應該就這樣作. 根據觀點不一樣, 它提供了普遍的應用選擇.

  • 保持強大的向後兼容性. Spring演化通過精心設計和管理, 能夠防止版本之間出現破壞性改變. Spring支持必定範圍版本的JDK和第三方庫. 便於維護依賴於Spring的程序和庫.

  • 關心API設計. Spring團隊花費大量精力和時間設計API, 使其直觀而且能保持多個版本和持續不少年.

  • 高質量的編碼, Spring強調有意義的, 實時的,準確的javadoc. 是極少數聲稱代碼簡潔且包之間沒有循環依賴的項目之一.

4. 反饋和貢獻

對於如何操做或診斷或調試問題, 咱們強烈建議使用StackOverflow, 而且咱們有一個問題頁清單, 列出了使用建議. 若是你徹底肯定Spring Framework有問題或者想提交特性, 請使用JIRA問題跟蹤.

若是你解決了問題或者修正了建議, 你能夠在GitHub上發起PR. 總之,請記住, 除了微不足道的問題,咱們但願有個問題存根進行討論並記錄下來供將來參考.

更多問題請參看頂級頁面"貢獻"頁上的指南.

5 入門

若是你剛開始使用Spring, 你可能想要經過建立一個Spring Boot的項目開始Spring之旅. Spring Boot提供了一個快速(也是固化的)方式建立生產就緒的 Spring 程序, 它基於Spring 框架, 信奉約定優於配置,而且設計爲快速啓動運行.

你能夠使用start.spring.io來生成基礎項目, 或者按照"入門"指南一步步建立, 例如"Getting Started Building a RESTful Web Service". 這些指南只關注於當前主題任務, 能夠比較容易的理解, 不少都是Spring Boot項目. Spring portfolio還包含其餘項目, 當你解決特定問題時你可能會考慮關注下相關的項目.


核心技術

這部分指導文檔涵蓋了Spring Framework不可或缺的全部技術

這些技術中最重要的,是Spring Framework的Ioc容器. 在吃透了Spring Framework 的IoC容器以後,緊接着是理解Spring的AOP技術. Spring Framework有其自身的AOP框架, 概念上很好理解而且可以知足實際應用中80%的熱點須要.

Spring提供了AspectJ集成(這是目前特性最爲豐富,固然也是Java企業領域最成熟的AOP實現).

1. IoC容器

本章涵蓋Spring的IoC容器.

1.1 介紹Spring IoC容器和Beans

本節涵蓋了Spring Framework對IoC原則的實現. DI是與其密切相關的另外一個概念. IoC是一個處理過程,經過這個過程,對象只能經過構造函數參數, 工廠方法參數或在從工廠方法構造或返回的對象實例上設置的屬性來定義他們的依賴關係. 當建立這些bean時, 容器去注入這些依賴. 這個過程從根本上反轉了由對象自身去控制它所需依賴的方式, 經過直接類構造或相似Service Locator模式的機制.

org.springframework.beansorg.springframework.context 這兩個包是IoC容器的基礎. BeanFactory 接口提供了可以管理任何對象類型的高級配置機制. ApplicationContextBeanFactory 的一個子類接口. 增長如下功能:

  • 易於與Spring的AOP特性集成.
  • 消息資源處理(國際化)
  • 事件發佈
  • 應用程序層次的特定上下文,例如:在Web程序中的WebApplicationContext.

簡言之, BeanFactory 提供了配置框架和基本的功能, ApplicationContext 增長了諸多企業特性功能. ApplicationContextBeanFactory 的一個完整超集, 在本章中專門用於Spring IoC容器的描述. 若是想用BeanFactory代替ApplicationContext能夠參看後面有關BeanFactory的內容.

Spring中,構成你程序的骨架而且被Spring IoC容器管理的對象被稱爲beans. bean就是一個被Spring IoC容器實例化,裝配和管理的對象. bean也能夠簡單的是應用中諸多對象中的一個.bean和他們之間的依賴被映射到容器的配置元數據中.

1.2 容器概覽

org.springframework.context.ApplicationContext 接口表明了Spring IoC容器而且負責實例化,配置,組裝bean. 容器經過讀取配置元數據獲取指令來實例化,配置,組裝bean.配置元數據使用XML,Java註解或者Java代碼的方式表現.它容許您表達組成應用程序的對象以及這些對象之間豐富的依賴.

Spring提供了ApplicationContext 接口的幾個實現. 在獨立應用中, 一般會建立一個ClassPathXmlApplicationContextFileSystemXmlApplicationContext的實例.XML是傳統的定義配置的格式, 你也能夠經過一小段XML配置來啓用這些支持的格式, 指定容器使用Java註解或者代碼格式配置.

在不少的應用場景下, 並不須要顯式的實例化一個或多個Spring的IoC容器. 例如, 在Web應用中,web.xml文件中大概八行相似的樣板化的XML描述就足夠了(參看Web程序中便捷的ApplicationContext實例). 若是你使用Spring Tool Suite(一種Eclipse加強開發環境), 可以很輕鬆地用幾回點擊鼠標和幾個按鍵生成這樣的樣板配置.

下圖從較高層次展現了Spring如何工做. 你的程序類和配置元數據時結合在一塊兒的, 所以,當ApplicationContext建立並實例化後, 你就有了一個可執行系統或程序.
The Spring Ioc Container

1.2.1 配置元數據

如同上圖展現的, Spring IoC 容器使用配置元數據. 配置元數據表現了你做爲開發者如何告知Spring容器去實例化,配置並組裝程序中的對象.

配置元數據以傳統而直觀的XML格式提供, 這是本節大部份內容傳達的關於Spring IoC容器的關鍵概念和特性.

XML不是惟一容許描述元數據的格式. Spring IoC 容器已經弱化了配置以何種格式書寫. 當今,許多開發人員更願意在程序中選擇Java配置的方式.

如何使用其餘格式的配置,能夠參考下面的信息:

  • 註解配置: Spring 2.5引入了註解配置支持
  • Java配置: 從Spring3.0開始, Spring JavaConfig項目中的一些特性已經成爲Spring Framework的核心. 所以,你能夠使用Java而不是XML文件擴展你的應用. 要使用這些新特性, 請參看@Configuration,@Bean,@Import,@DependsOn註解.

Spring配置由至少一個,或典型的超過一個由容器管理的bean的定義. XML格式使用<bean/>元素配置這些beans, 它嵌套在頂層<beans/>元素裏面. Java配置則包含在使用@Configuration註解的class中,並使用@Bean註解方法.

這些bean定義與構成你程序的對象相吻合. 例如, 你定義服務層對象,數據訪問層對象,變現層對象如Struts Action 實例, 基礎對象如Hibernate SessionFactories, JMS 隊列等. 通常不會在容器中定義細粒度的域對象.由於這一般是DAO或業務邏輯層的任務去建立和加載這些對象. 儘管如此, 你能夠使用AspectJ去配置在容器以外建立對象.參看在Spring中使用AspectJ依賴注入領域對象.

下面例子展現了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="...">   
        <!-- collaborators and configuration for this bean go here
        1. id是區分bean的一個字符串標識符
        2. class 定義bean的類型,使用全限定類名
         -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

id屬性的值指向協做的對象. 本例中沒有明確寫出.可參看依賴項.

1.2.2 實例化容器

ApplicationContext 構造參數中的定位參數字符串是讓容器從外部變量加載配置. 參數能夠是多種資源格式, 例如本地文件系統, Java CLASSPATH等.

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

學習Spring容器後, 你可能想要了解下Spring的Resource抽象, 它提供了一種便捷的從URI格式的資源中讀取流的機制. 尤爲是,Resource路徑一般用來構建程序上下文, 這點可參看"程序上下文和資源路徑"

下面例子展現了服務層對象的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

接下來的例子展現了數據訪問層配置:

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

前面例子中, 服務層由PetStoreServiceImpl類和兩個數據讀取對象JpaAccountDaoJpaItemDao(根據JPA對象關係映射標準). name指類中的屬性,表示bean的名稱, ref元素指向另外一個bean定義. 在idref元素之間的聯繫代表了對象間的協做依賴關係. 關於對象依賴配置的更多細節, 參看"依賴項".

結合XML格式的配置

使用多個xml文件定義bean是有用的. 一般各自的xml文件能分別表示邏輯層或架構中的一個模塊.

你能夠使用程序上下文構造器從全部這些XML片斷中加載bean的定義. 構造器獲取多個resource資源位置, 就像咱們在上節展現的那樣. 或者, 使用一個或多個<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. 這些文件的路徑相對於導入他們的文件, 所以services.xml 必須是與導入文件處在相同路徑目錄下. 就像你看到的, '/'能夠忽略. 雖然路徑是相對的,但儘可能不要使用'/'. 導入的這些文件的格式必須符合Spring的Schema, 須要有頂級<beans/>元素. 必須是有效的XML的bean定義.

能夠但不提倡在父級目錄中使用'../'引用文件. 這樣會在當前應用程序外建立依賴文件.特別不提倡使用classpath:URLs(例如,classpath:../services.xml),運行時解析時會選擇最近的根路徑而且轉到它的父目錄.Classpath的配置可能會錯誤地引導到其餘的目錄下.
能夠使用絕對路徑替代相對路徑,例如file:C:/config/services.xmlclasspath:/config/services.xml. 但這樣就不靈活地將路徑耦合到程序配置中了.通常的作法是能夠使用一種間接方式-例如佔位符"${...}", 這樣系統JVM能夠在運行時解析到正確路徑上

命名空間自己提供了導入指令特性. 比純bean定義更高級的配置特性Spring也有提供. 例如contextutil命名空間.

Groovy的Bean定義DSL

外部化元數據配置的更高級例子, bean定義也能夠使用Spring的Groovy Bean Definition DSL, 因Gails框架而熟知. 下面演示了".groovy"文件中典型的配置的方式:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

這種配置的風格大致上與XML配置相同, 甚至支持XML命名空間. 能夠經過importBeans指令從XML文件導入bean定義.

1.2.3 使用容器

ApplicationContext接口是一個能管理註冊的bean以及他們之間依賴的高級工廠. 經過方法T getBean(String name, Class<T> requiredType) , 能夠獲取Bean的實例.

ApplicationContext容許讀取bean定義並訪問他們, 以下所示

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

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

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

Groovy配置的啓動也相似. 不過它有Groovy風格的不一樣上下文實現(同時也支持XML). 下面展現了Groovy配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最靈活的變量是GenericApplicationContext, 其中包含讀取器代理, 例如: 對於XML文件, 它使用XmlBeanDefinitionReader讀取. 以下例:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

對於Groovy, 但是使用GroovyBeanDefinitionReader, 以下所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

在相同的ApplicationContext中能夠混合使用這些讀取代理器, 從而從不一樣資源中讀取配置.

能夠使用getBean獲取bean的實例. ApplicationContext接口還有一些其餘的方法獲取bean, 可是理想狀態下, 你的程序應該永遠不要使用它們. 確實, 你的程序代碼壓根不該該調用getBean方法,所以就一點也不會依賴Spring API. 例如, Spring爲多種Web框架的組件集成提供DI功能, 例如controller和JSF管理的Bean, 容許你經過元數據聲明依賴的bean(相似包裝(autowiring)註解).

1.3 Bean 概覽

Spring IoC 容器管理一到多個bean. 這些bean是根據你提供給容器的配置建立的. (例如, 經過XML格式的<bean />定義)

在容器內部, 這些bean定義表現爲BeanDefinition對象. 其包含以下信息 :

  • 包含包名的類名: 典型地,bean定義的實際實現類;
  • Bean行爲配置元素, 標記bean在容器中行爲(做用域scope, 生命週期回調等);
  • bean的協同或依賴的其餘bean的引用.
  • 最近建立對象的其餘配置信息,例如當使用bean時鏈接池的池大小或連接數

元數據被解析爲一系列組成bean定義的屬性, 下面表格列出了這些屬性:

表 1. Bean定義中的屬性

屬性 參看
Class 初始化bean
Name 命名bean
Scope Bean的做用域
Constructor arguments 依賴注入
Properties 依賴注入
Autowiring mode 自動裝配協做對象
Lazy initialization mode 懶加載Bean
Initialization method 初始化回調
Destruction method 銷燬回調

bean定義包含如何建立特定bean, 除此以外ApplicationContext的實現容許將容器外建立的bean註冊進來. 這是經過getBeanFactory()方法訪問ApplicationContext的 BeanFactory , 該方法默認返回DefaultListableBeanFactory 實現. DefaultListableBeanFactory支持經過registerSingleton(..)registerBeanDefinition(..) 方法註冊. 儘管能夠這樣作,應用程序通常仍是單純使用規則的bean定義元數據.

bean元數據和單例的手工支持必須儘早註冊, 這是爲了容器可以在自動裝配和其餘自省階段合理解析.重寫已經存在的元數據以及已經存在的單例在某些級別上是支持的, 但運行時註冊新的bean(與工廠的併發訪問)沒有獲得正式的支持,並且可能致使併發訪問異常或bean狀態不一致,或二者都有.

1.3.1 命名Bean

每一個bean都有一個或多個標識符. 容器內這些標識符必須是惟一的. 通常一個bean只有一個標識符. 可是也能夠有多個,多出來的標識符是別名.

XML配置中,idname屬性用來作標識符. id用來精確指定一個id. 按照習慣, 這些名稱是字母數字組成的('myBean', 'someService', etc.), 但他們也能夠包含特殊字符. 若是你想給bean指定別名,你能夠將他們賦值給name屬性, 用逗號,分號或者空格分割. 在Spring3.1以前, id是定義爲一個xsd:ID類型, 只能是字母. 自從3.1開始將其定義爲xsd:string類型. 注意id的惟一性依然是容器強制的, 而不是XML解析器.

給beannameid屬性不是必須的. 若是沒有指定, 容器會爲bean生成一個惟一名稱. 儘管這樣, 若是你想經過名稱引用bean, 或者經過ref元素或者是Service Locator風格的查找, 你就必須給bean指定名稱. 不給bean指定名稱的動機是使用內部類和自動裝配.

bean命名約定

給bean命名遵循與給實例域命名相同的約定. 也就是,使用小寫字母開頭的駱駝命名法. 例如: accountManager, accountService, userDao, loginController等.

堅持命名bean能夠使你的配置易讀易理解. 還有, 若是使用Spring AOP, 當給名字相關的一些列bean應用通知時也會有很大幫助.

掃描類路徑時, Spring會給未命名組件生成名稱, 遵循前面描述的規則: 本質上是取類名,而後將首字符小寫. 當有多餘一個字母而且第一個和第二個字母都是大寫時將保留大小寫. 這些規則定義在java.beans.Introspector.decapitalize(Spring使用)

在Bean定義以外添加別名

在bean定義內, 你能夠給它指定多個名稱, 能夠使用給id指定一個名稱, 同時也能夠給name指定多個(使用分隔符).使用這些名稱指定的bean都是等效的, 在某些狀況下也是有用的, 例如: 讓應用程序中的每一個組件經過使用特定於該組件自己的bean名稱來引用公共依賴項。

然而在定義bean的時候爲其指定別名有時候並不夠. 有時候須要將別名bean定義外的其餘地方指定. 一般的例子是, 在大型系統內各個子系統分別有各自配置, 每一個子系統有一組其bean的定義. XML的配置中,你能夠使用<alias/>元素實現. 以下:

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

本例中, bean的名稱(同一容器)被命名爲fromName, 在使用別名定義後, 這個bean經過toName也能夠引用.

例如, 子系統A引用了一個數據源叫subsystemA-dataSource. 子系統B引用數據源叫subsystemB-dataSource. 當主程序都使用這兩個系統時, 主程序引用了數據源myApp-dataSource. 這三個數據源都指向相同的對象, 你能夠將下面的別名配置添加到元數據:

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

如今, 雖然每一個組件和主程序都經過一個名稱引用了各自惟一的數據源, 而且能保證不會與其餘定義衝突(有效建立了命名空間), 然而實際上他們引用的同一個對象.

Java 配置

若是使用Java配置, @Bean註解能夠提供別名, 參看:如何使用@Bean註解.

1.3.2 實例化Bean

bean定義的本質是建立對象的配方. 當須要時容器將查看bean的配方, 並使用該bean的定義封裝的配置元數據來建立(或獲取)實際對象.

若是使用XML配置, 要實例化的對象的類型是經過<bean/>節點的class屬性來指定的. class屬性(對應到BeanDefinition實例是Class屬性)一般是強制的. (例外的狀況請參看:使用工廠方法實例化,和Bean定義的繼承.) 有兩種使用Class屬性的方法:

  • 典型的, 直接指定class, 由容器經過構造器反射的形式直接建立bean. 有點等同於java代碼的new操做.
  • 爲包含建立對象的靜態工廠方法指定對象的實際類, 少數狀況下容器經過靜態工廠方法建立bean. 被靜態工廠方法建立出來的對象多是相同的類型或者壓根是另外一個類型.

內部類名稱

若是你想要爲一個靜態內部類配置bean, 你就必須使用內部類的雙名稱.

例如, 你有個類定義爲SomeThing,在com.example包下. SomeThing有個靜態內部類爲OtherThing, 那它的class屬性的值將是com.example.SomeThing$OtherThing

注意內部類和外部類之間須要使用$字母分割.

使用構造器實例化

當使用構造器建立bean時. 全部標準類均可用且都是可與Spring兼容的. 也就是開發時不須要實現任何接口或者遵循特定的編程風格. 簡單定義爲一個class便可. 儘管如此, 根據使用的IoC容器, 可能你須要定義一個默認構造器.

IoC容器能夠管理任何你想要被託管的類. 它不只限於管理JavaBeans. 大多數Spring用戶喜歡在屬性後定義getter和sertter模塊. 你也能夠在容器中定義非bean風格的類. 例如, 若是你想使用遺留代碼中徹底不遵循JavaBean規範的鏈接池, Spring也是能夠管理的.

使用XML配置指定bean定義 以下:

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

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

更多關於構造函數參數和對象構造後屬性的賦值, 參看:依賴注入.

靜態工廠方法實例化

當定義使用靜態工廠構建的bean時, 須要使用class屬性指定包含靜態工廠方法的類, 而且使用factory-method屬性指定工廠方法.你能夠調用該方法(帶可選參數, 後面有表述)返回一個對象, 接着這個對象就能夠像使用構造器建立出來的同樣使用了. 一種這麼使用bean定義的場景是在遺留代碼中調用靜態工廠.

下面指定了經過調用靜態工廠方法建立bean的配置. 這個定義沒有指定返回類型, 只是指定了這個類包含靜態方法. 在本例中, createInstance()方法必須是靜態方法. 下面例子展現瞭如何指定靜態方法:

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

有關靜態方法的參數和對象從工廠返回後屬性的賦值, 參看: 依賴和配置細節.

使用對象工廠方法實例化

與靜態工廠方法實例化相似.容器能夠經過調用已存在的bean的非靜態工廠方法去建立bean. 要使用這種機制, 能夠將class留空, 而且在factory-bean屬性指定當前(或父或祖先)容器中包含用來建立對象的工廠方法的bean. 使用factory-method屬性設置工廠方法的名稱. 下面展現了怎麼配置:

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

下面代碼展現了對應的java類:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

工廠類能夠有多個工廠方法的定義, 以下所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

下面是對應的java類:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

關於工廠bean自己如何經過DI管理和配置, 參看:依賴和配置細節.

Spring文檔中,"factory bean"(工廠bean)是指Spring容器中配置的用來經過實例或靜態工廠方法建立對象的bean. 相比而言, FactoryBean(注意大小寫)指Spring特有的FactoryBean

1.4 依賴

今後處開始升級到5.2.0版本

典型的企業應用不是由單個對象組成的(或用Spring語法來講的bean).就算是最簡單的程序也有一些終端用戶看起來相互合做的對象來呈現.接下來的這節闡述如何在一個真實的系統中定義一系列的bean, 從而讓他們協做達成目標.

1.4.1 依賴注入

依賴注入是一個處理過程, 在對象被構造後或者從工廠返回後, 僅僅經過構造函數參數, 工廠方法的參數或者屬性賦值的方式來定義他們的依賴(也就是與其餘對象協做). 容器在建立bean後注入他們的依賴. 這個處理過程本質上是bean本身去使用類構造和服務定位模式管理初始化和定位它的依賴項的反轉(所以叫控制反轉).

使用DI原則的代碼是清晰的, 而且作到了與提供的依賴項更有效地解耦. 對象不本身定位查找依賴項, 也不知道依賴項的位置和類型.所以, 你的類就更容易被測試, 特別是依賴於接口和抽象類的狀況下, 容許你單元測試中使用樁對象或模擬實現.

DI有兩個主要的變種: 構造函數依賴注入和屬性Setter依賴注入.

構造函數依賴注入

構造函數注入是經過容器調用有若干參數的構造函數完成的, 每一個參數表明一個依賴項. 調用帶參的靜態工廠方法構造bean與此十分相似, 這裏討論對待構造函數構造和靜態工廠方法構造是類似的. 下例展現了構造器注入的類定義:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

注意這個類沒啥特別之處. 它自己就是一個沒有實現容器相關接口,基類或使用註解的普通POJO.

構造參數解析

構造參數是經過類型解析匹配的. 若是bean的構造參數沒有潛在的二義性, 那麼在bean中定義的參數順序就是bean初始化時的參數順序. 看下面的代碼:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假設ThingTwoThingThree沒有繼承關係, 沒有潛在的二義性. 所以, 下面的配置能很好的起做用, 在<constructor-arg/>元素中你不須要制定構造參數的索引或明確制定其類型.

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

當另外一個bean被引用時, 類型是已知的,匹配能發生(就像前面例子中的處理過程). 當使用簡單類型時,例如<value>true</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>

另外, 若是參數有多個簡單類型, 能夠使用索引解決多個簡單類型參數的二義性.

參數是從0開始的.

構造參數名稱

也能夠使用指定構造參數名稱消除二義性, 以下:

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

記住, 要不受限制的使用此功能, 你的代碼必需要啓用debug標記編譯, 這樣Spring才能從構造函數查找參數名稱. 若是不能或不想啓用debug標記, 能夠使用@ConstructorPropertiesJDK註解顯式添加到構造函數的參數上. 看起來如同下面例子:

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或靜態工廠方法返回bean以後, 再由容器調用bean的setter方法.

下例展現了只能用純setter方式進行注入的類定義. 這個類是傳統的java. 是一個沒有實現容器相關的接口,基類或添加註解的POJO.

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext支持構造函數注入和Setter注入. 也支持經過構造函數注入部分依賴後再由Setter注入. 你使用BeanDefinition類的形式配置依賴, 這個類與PropertyEditor實例協做將屬性從一種格式轉化爲另外一種格式. 儘管如此, 大多Spring用戶不會直接使用這些類(在編程方面), 而是使用XML bean定義, 或者註解註釋組件(也就是使用@Component,@Controller等),或者用@Configuration註解的類中使用@Bean的java代碼配置. 這些資源將在內部轉化爲BeanDefinition實例並用於在IoC容器實例加載.

構造器注入仍是Setter注入

由於能夠同時混用構造器和Setter注入, 一種比較好的原則是: 強制的依賴使用構造器注入, 可選的依賴則能夠使用setter或配置方法. 注意: setter方法上使用@Required註解將使得對應屬性成爲必須的依賴.

Spring團隊提倡構造器注入, 這會讓你實現的程序組件是不可變對象而且能夠保證依賴不是null. 更多的好處是, 構造器注入返回給客戶端的組件老是徹底被初始化的狀態. 還有個邊緣效應, 就是大量的構造函數參數是一種很差的代碼味道, 暗示着這個類承載了太多的責任而且須要被合理的重構, 實現關注點分離.

Setter注入應該在依賴是可選的前提下優先使用, 這些依賴能夠被賦以合理的默認值. 另外, 非null驗證必需要在代碼使用依賴的全部地方進行. 使用setter注入的一個好處是: setter方法使得對象的重配置和重注入更易於控制. 經過JMX MBeans管理就是一種setter注入的案例.

使用DI使得特定類更加有意義. 有時候, 當你使用三方類庫時, 你並無源代碼, 此時你將手握DI選擇權. 例如, 若是三方類沒有暴露任何setter方法, 那麼使用構造器注入將是惟一途徑.

依賴解析過程

容器按照下面闡述的對bean依賴進行解析:

  • ApplicationContext被建立, 而且使用配置的bean元數據進行初始化. 配置元數據能夠是XML,Java code或註解.
  • 對於每一個bean, 它的依賴以屬性,構造函數參數或者靜態工廠的參數(若是使用代替普通的構造函數)形式表現. 這些依賴將在bean被建立後提供給bean.
  • 每一個屬性或參數是要被設置的值的定義, 或者是容器中另外一個bean的引用.
  • 每一個屬性或參數的值是由特定的格式轉換到實際類型的. 默認狀況下, Spring可以將字符串格式轉化爲全部內置類型如: int, long, String, boolean等.

Spring容器在建立後對每一個bean的配置進行校驗. 儘管如此, 在bean被建立後bean的屬性纔會被賦值. 單例域的而且設置爲預實例化(默認狀況)的bean在容器建立後被建立. 域的定義參看bean的做用域. 除此以外, bean只有在被請求時才建立. 一個bean的建立會潛在地致使bean的整個圖被建立, 也就是bean的依賴,它的依賴的依賴等都被建立和分配. 注意: 依賴解析的不匹配可能會後期表現出來, 也就是第一次建立受影響的bean時.

循環依賴

若是用占主導地位的構造器注入, 就可能會致使沒法解析的循環依賴.

例如: 類A須要類B,經過構造器參數注入, 相反類B也須要類A,經過構造器注入. 若是配置A,B相互注入給對方, Spring的IoC容器就會在運行時檢測到循環引用, 並拋出BeanCurrentlyInCreationException.

一種處理這種狀況的方法是編輯源代碼, 將一個的setter注入改成構造器注入. 相應地避免使用構造器注入而僅僅使用setter. 換句話說, 雖然不提倡, 但能夠使用setter注入配置循環依賴.

不一樣於典型的案例(即沒有循環依賴), 在bean A和bean B之間的循環依賴將迫使bean在徹底實例化自身前將其注入給對方(這是典型的雞生蛋蛋生雞的場景).

整體上你能夠信任Spring去作正確的事情. 它在容器加載期間檢測配置問題, 如不存在的bean或循環依賴.當bean被真正建立後, Spring會盡可能延後設置屬性和解析依賴. 這意味着在容器正確加載後, 若是你請求的bean有問題或它的依賴有問題,可能會拋出異常--例如, bean拋出缺失或無效屬性的異常. 這種潛在的配置問題的延遲致使的不可見性就是爲何ApplicationContext的實現默認會預實例化單例bean. 這種會致使前期一些時間和內存消耗的代價能換來配置問題的及時發現, 就是在ApplicationContext被建立時, 而不是之後調用bean時. 你也能夠覆蓋這種預初始化的配置爲延遲初始化.

若是沒有循環依賴, 當一個或多個協做的bean被注入到依賴的bean裏面, 每一個協做bean實際上是優先於依賴bean就被徹底配置好了. 這意味着若是A依賴B, 容器會在調用A的setter方法以前徹底配置好B. 換句話說,這個bean被實例化了(若是它不是預實例化的),它的依賴被設置了, 而且他的生命週期函數(如配置的初始化函數或初始化bean回調函數)也被調用了.

DI的例子

下例使用XML配置setter形式的DI. 一小段bean的定義以下:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

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

下面展現了對應的ExampleBean類:

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

上例中, setter聲明爲匹配xml文件中指定的屬性, 下面例子使用構造函數注入:

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

下面展現了對應的ExampleBean類定義:

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

下面展現了對應的ExampleBean類:

public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

給靜態工廠方法的參數是<constructor-arg/>元素提供的, 就像使用構造函數同樣. 工廠方法返回的實例類型不須要與包含靜態工廠的類的類型一致(本例一致). 一個(非靜態)實例工廠本質上使用相同方式(除了使用factory-bean而不是class屬性),因此咱們不討論這些細節.

1.4.2 依賴和配置細節

在前面的章節中提到, 你能夠定義bean的屬性或者經過構造函數參數去引用另外的bean(協做者)或者在行內書寫數據值. 爲了能這樣作, Spring的XML配置能夠使用<property/><constructor-arg/>元素包含在bean定義元素中.

純值數據(原始數據,String等類型)

元素<property/>的屬性value能夠指定爲書寫或構造參數的純值數據. Spring的轉化服務用來將這些值從String轉化爲合適的類型. 下例展現了一組這樣的配置:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

下例使用p-命名空間展現更簡潔的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更爲精簡. 類型轉化是發生在運行時而不是設計時, 除非你使用可以在定義bean時支持屬性自動完成的IDE(就像IntelliJ IDEA 或 Spring Tool Suite). 而這樣的IDE輔助是咱們提倡的.

也能夠配置java.util.Properties類型的實例, 以下:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring 容器轉化內部<value/>元素的內容爲java.util.Properties的實例, 這是經過使用JavaBeans的PropertyEditor機制來實現的. 這是一個捷徑, 也是幾種Spring團隊喜歡使用嵌套<value/>而不是value屬性風格的狀況之一.

idref元素

idref元素僅是一種防止錯誤的方法, 能夠將容器中的另外一個bean的id(一個字符串,不是引用)傳遞給屬性或構造參數. 以下所示:

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上面bean的定義徹底等效於(在運行時)下面的片斷:

<bean id="theTargetBean" class="..." />

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

第一種形式優於第二種, 由於使用idref標籤可以讓容器在部署期間檢查引用的bean是否是真的存在. 第二種狀況下, 傳遞給bean名爲clienttargetName屬性的值不會被校驗. 僅僅是在client被實際實例化的時候會發生類型轉化(大多數狀況下是嚴重錯誤). 若是clientbean是個原型bean, 則只有在部署容器好久以後纔會發現錯誤和由此產生的異常.

idref元素的local屬性再也不支持4.0版本的beans XSD, 由於它再也不提供常規bean引用的值. 當升級到4.0時, 請修改現有的idref local引用, 修改成idref bean.

<idref/>元素帶值的一個地方(至少在Spring2.0以前的版本中)是在ProxyFactoryBean定義中AOP攔截器的配置. 當你爲了防止拼寫錯攔截器ID而指定攔截器名稱時使用<idref/>元素.

引用其餘bean(協做者)

ref元素是在<constructor-arg/> 或者 <property/>中的不可更改的元素. 在這裏, 你將另外一個由容器管理的bean(協做者)做爲引用的值賦給一個bean的屬性. 被引用的bean做爲引用被賦值給這個bean的屬性, 而且它是在被賦值前就按需初始化的. (若是這個協做者是個單例的話,它已經被容器初始化了).全部引用最終都是另外一個對象的引用. 做用域和校驗則取決於你是否經過bean,local,partent屬性爲另外一個對象指定ID或名稱.

經過<ref/>tag的bean屬性指定目標bean是常見的方式, 而且容許其在同一個容器或父容器中建立任何bean的引用. 無論是否是配置在XML格式的文件. bean屬性的值能夠是目標bean的ID後者是name中的任一值. 下面展現了ref元素:

<ref bean="someBean"/>

經過parent屬性建立的引用指定目標bean是在當前容器的父容器. 其值多是目標bean的id或name的其中任一值. 目標bean必須在當前容器的父容器中. 主要使用這個屬性的場景是: 當你使用了有層次的容器而且在父容器中經過代理proxy包裝了一個同名的父bean. 下面是一對使用parent的例子:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

4.0的beans XSD 後ref元素的local屬性再也不支持, 所以不須要用它爲一個正常的bean引用提供值了. 當升級到4.0時請注意修改現有的ref localref bean.

內部的bean

<bean/>元素若是定義在<property/>或者<constructor-arg/>內部, 則表示定義了內部bean, 以下所示:

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

內部bean定義不須要指定ID或name, 若是指定了, 容器也不會使用它們做爲bean的標識符. 容器也會在建立時忽略它們的scope標記, 由於內部bean一般都是匿名的, 而且老是跟外部bean一塊兒建立. 通常不可能去單獨的訪問內部bean, 或者將他們注入到協做bean而不是包裝bean中.

做爲旁例, 從一個自定義的scope中獲取到銷燬回調是可能的, 例如對於一個單例bean中做用域爲request-scope的內部bean. 內部bean的建立是與外部bean的建立是綁定的, 可是銷燬回調使它特定於request生命週期. 這並非一個廣泛的場景, 內部bean通常是與包含它的bean有着相同的做用域.

集合

<list/>,<set/>,<map/><props/>元素分別
對應於Java集合類型(Collection)的List, Set, Map, 和 Properties. 下例展現其用法:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

字典map的鍵值,或者集set的值, 能夠是下面元素的任一:

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

Spring容器支持合併集合. 開發人員能夠定義一個父的<list/>,<set/>,<map/><props/>元素,而且子元素的<list/>,<set/>,<map/><props/>能夠繼承和覆蓋父集合的值. 也就是說, 子集合的值是父集合與子集合元素的合併, 子集合的元素覆蓋了父集合中的值.

本節討論父子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>

注意merge=true的使用, 它在beanchild<props>元素中名爲adminEmails. 當child被容器解析和初始化後, 最終的實例將有一個adminEmailsProperties集合, 包含了合併父集合與子集合中adminEmails的所有元素. 下面展現告終果:

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

Properties集合的值繼承了父<props/>, 而且子集合中support值覆蓋了父集合中的值.

這種合併行爲也一樣相似於<list/>, <map/>, 和 <set/>等集合類型. 在<list/>元素的特定狀況下, 其語義與List集合類型相關聯(也就是一個一系列值的有序集合). 父集合的值優先於子集合的值. 在Map, Set, 和 Properties類型狀況下, 元素間不存在順序. 所以,容器內部使用構築在Map,Set,Properties實現類型之上的無序集合類型.

集合合併的限制

不能合併不一樣的集合類型(如MapList進行合併). 若是這樣作, 會拋出相應異常. merge屬性必須在繼承或者較低的子定義上. 指定在父集合定義上的merge是多餘的,也不會產生指望的合併.

強類型集合

從Java5的泛型集合開始, 你能夠使用強類型的集合了. 也就是聲明一個值包含(例如)String 類型的元素集合成爲可能. 若是使用Spring的DI去注入一個強類型集合, 你能夠獲得Spring類型轉化支持, 將先前添加到Collection的元素轉化爲正確的類型. 下例展現了用法:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <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>

當beansomethingaccount屬性準備注入時, 它的泛型類型信息被反射獲取到. 所以, Spring的類型轉化基礎設施辨別出元素的值是Float, 而且將字符串值(9.99,2.75 和 3.99)轉化爲實際的Float類型.

Null和空字符值

Spring將properties的空參數視爲空字符串. 下面的XML配置片斷設置email屬性爲空值("").

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

上面的例子等效於下面的Java代碼:

exampleBean.setEmail("");

<null/>元素處理null值. 以下所示:

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

上面代碼等效於:

exampleBean.setEmail(null);
XML的p-命名空間

p-命名空間能夠在bean元素的屬性中使用,(而不是嵌套的<property/>元素), 能夠用來描述屬性值或協做bean.

Spring支持用命名空間擴展配置格式, 這基於XML架構定義. 這節討論的bean配置格式定義在XML架構文檔中. 儘管如此, p-命名空間沒有定義在XSD文件中, 值存在於Spring的核心.

下面展現了兩段XML(第一段是標準的XML, 第二段是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="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

這個例子展現了bean定義中有個p-命名空間叫email. 這實際是告訴Spring有個屬性聲明. 如前面提到的, p-命名空間沒有架構定義, 所以你能夠用屬性的名字設置標籤屬性名稱.

下面展現了兩個bean定義中同時引用了另外一個bean.

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

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

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

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

這個例子包含了不止是用p-命名空間的一個屬性, 並且使用了聲明屬性引用的格式. 第一個bean定義使用<property name="spouse" ref="jane"/>建立了從beanjohnjane的引用, 第二個定義則使用了p:spouse-ref="jane"來完成相同的事情. 本例中, spouse是屬性名, 同時-ref代表這不是一個表值而是對另外一個bean的引用.

p-命名空間不如標準xml格式靈活. 例如, 聲明屬性引用的這種格式與使用ref的格式衝突. 而標準的XML則不會. 咱們強烈建議你細心選用合適的方式, 與團隊溝通來避免產生同時使用三種格式的XML文檔.

XML的c-命名空間

相似於p-命名空間. 從Spring3.1開始, c-命名空間容許將構造參數配置在行內, 而不是單獨嵌套的<constructor-arg/>元素內.

下例使用c:命名空間實現與構造函數注入相同的事情:

<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="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

c:命名空間使用與p:命名空間相同的約定來設置構造函數參數(對於引用使用-ref後綴).類似的, 須要聲明在XML文件中, 雖然在XSD架構中未定義(它存在於Spring的內核中).

對於少數構造函數名稱不可用的狀況(一般是沒有debug信息的已編譯二進制文件),能夠使用參數索引, 以下:

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>

鑑於XML語法, 索引必須以_開頭, 由於XML屬性名稱不能以數字開頭(雖然一些IDE能夠). 相應的,元素<constructor-arg>中也可以使用索引數字,但不經常使用, 由於聲明時的順序已經足夠了.

實踐中, 構造函數解析機制對於匹配參數已經足夠了, 因此除非你真正須要, 咱們建議使用名稱標記貫穿整個程序.

複合屬性名稱

在設置bean屬性時能夠使用複合或者嵌套的屬性名稱. 只要路徑下全部組件不爲null便可. 以下定義:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

somethingbean有個fred屬性, fred又有個bob屬性, bob有個sammy屬性, 最終sammy屬性被賦值爲123. 爲了能使其起做用, 在bean被構建後, fred和其bob必須不爲null.不然NullPointerException將被拋出.

1.4.3 使用depend-on

若是一個bean是另外一個bean的依賴, 也就意味着一個bean會做爲另外一個bean的屬性值. 在XML配置中你使用<ref/>元素來配置. 但有時候bean之間的依賴關係不是直接的. 舉個例子, 一個類的靜態初始化器須要被觸發,好比對於數據庫驅動註冊. depends-on屬性可以顯式的迫使一個或多個bean的初始化, 這發生在使用了這個元素的bean初始化以前. 下例使用depends-on展現只有一個bean的依賴:

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

對於多個依賴, 能夠爲depends-on屬性指定多個值(用分號,空格, 逗號分隔)

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

depends-on屬性可以指定初始化期間的依賴. 僅在單例bean中, 指定相應的銷燬期依賴. 用depends-on關係定義的依賴bean將被首先銷燬, 優先於它自身修飾的bean. 所以, depends-on能夠用來控制關閉順序.

1.4.4 延遲加載的bean

默認狀況下,ApplicationContext實現會立馬建立和配置全部單例bean, 做爲其初始化步驟的一部分. 一般,預初始化時使人滿意的, 由於配置和環境錯誤能夠被及時發現, 而不是通過幾小時,幾天. 當這種行爲不使人滿意時, 你能夠經過將bean定義爲延遲加載而阻止預初始化發生. 一個延遲加載的bean告知IoC容器,這個bean是在第一次請求時建立,而不是容器啓動時.

XML中, 經過<bean/>元素的lazy-init屬性控制這種行爲. 以下:

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

當上面的配置被ApplicationContext處理時, 其啓動時lazybean不會被當即初始化, 而not.lazybean將當即被初始化.

儘管如此, 當一個延遲初始化bean是一個非延遲初始化bean的依賴時, ApplicationContext在啓動時建立了延遲bean, 由於它必須知足單例的依賴. 延遲初始化bean被注入到非延遲的單例bean中了.

也能夠在容器級別經過<beans/>元素的default-lazy-init屬性控制延遲加載行爲. 以下所示:

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

1.4.5 自動裝配

Spring容器可以自動裝配協做bean之間的關係. 你可讓容器經過檢查ApplicationContext中的內容自動解析協做bean. 自動裝配有以下好處:

  • 自動裝配能夠顯著減小指定屬性和構造參數的須要. (對於本章其餘地方討論的bean模板等機制也是有價值的).

  • 自動配置能夠隨着對象發展而更新配置. 例如, 若是須要向類添加依賴,則能夠自動知足依賴而不需手工配置. 所以,自動裝配在開發階段頗有用, 不會在代碼變得更穩定時拒絕切換到顯式書寫的選項.

當時用XML配置時(參看依賴注入), 你能夠經過<bean/>元素的autowire屬性爲一個bean指定自動裝配模式. 自動裝配功能有四種模式. 你能夠任選其一. 下表描述了這四種模式:

表2. 自動裝配模式

模式 說明
no (默認)不使用自動裝配. bean的引用必須使用ref元素. 對於較大部署不推薦修改這個模式設置. 由於顯式指定的協做者提供了各大的控制權和可讀性. 某種意義上是一個系統的架構文檔.
byName 經過屬性名稱自動裝配. Spring經過名稱查找須要自動裝配的bean. 例如: 若是一個bean定義按名字自動裝配, 而且包含了一個master屬性(也就是同時有setMaster(..)方法), Spring會查找名爲master的bean定義, 而且將其設置到屬性.
byType 若是容器中有匹配property類型的bean就自動裝配. 若是多於一個, 將拋出致命異常, 這代表可能不該該使用byType匹配. 若是沒有匹配的bean, 則忽略(屬性不被賦值)
constructor byType相似可是是提供給構造函數參數的. 若是容器中沒有精確類型的bean, 致命錯誤將發生

經過byTypeconstructor裝配模式, 你能夠裝配數組和泛型集合. 這種狀況下, 容器中全部類型匹配的候選對象都將提供以知足依賴. 對於Map,若是key的類型是String,你就能夠自動裝配. 一個自動裝配的Map實例的值是由全部匹配類型的bean組成的, 這個實例的key包含對應bean的名稱.

自動裝配的限制和不足

自動裝配使用在貫穿整個項目的過程當中能工做得很好. 若是不是廣泛使用, 而只是使用到一兩個bean上時會搞得開發人員頭暈.

參考自動裝配的限制和不足:

  • propertyconstructor-arg設置的顯式依賴老是會覆蓋自動裝配. 不能自動裝配簡單類型,StringClass(或者這些類型的數組). 這個限制是專門設計的.

  • 比起顯式裝配, 自動裝配精確度較低. 雖然, 正如前面表格提到的, Spring很是當心地避免在多個指望結果下致使的二義性進行猜想. Spring管理的對象間的關係以及不是顯式文檔定義的了.

  • 裝配信息多是不可用的, 對於從Spring容器生成的文檔的工具而言.

  • 容器內多個bean定義可能會匹配到自動裝配的setter類型或構造參數類型.對於數組,或者Map實例, 這不是一個問題. 然而對於指望單一值的依賴, 這種二義性不能被隨意處理, 若是沒有惟一bean可用,異常將會拋出.

對於後面的幾種場景, 你可能有以下幾個選擇:

  • 拋棄自動裝配, 擁抱顯式裝配

  • 設置bean的autowire-candidatefalse以防止自動裝配, 正像下一節描述的.

  • 經過設置<bean/>元素的primary屬性爲true將其定義爲優先匹配對象.

  • 使用有更多細粒度控制的註解驅動配置, 如在註解配置中描述的.

從自動配置中排除bean

在單個bean級別, 你能夠從自動裝配中排除bean.在XML配置中, 能夠經過設置bean的autowire-candidatefalse. 容器能使得特定bean排除在自動裝配以外(包括註解格式的配置如@Autowired).

autowire-candidate屬性被設計爲僅對基於類型的裝配有效. 對於經過name引用的顯式引用無效, 即便指定的bean沒有被標記爲候選也會被解析. 結果就是當名字匹配時, 經過name自動裝配仍然會注入bean.

你能夠經過基於bean名稱的模式匹配去限制bean的自動裝配. 根級別元素<beans/>經過屬性default-autowire-candidate接收一個或多個模式.例如:限制名稱以Repository結尾的bean的候選狀態, 能夠使用模式*Repository. 能夠經過逗號分隔多個模式. bean元素上的autowire-candidate屬性的值truefalse老是有優先權. 對於這些bean, 模式規則不生效.

這些技術對於從沒想要經過自動裝配注入的bean是有用的. 這並不意味着被排除的bean本身不能經過自動裝配所配置, 而是它自己將不會做爲bean的候選裝配給其餘bean.

1.4.6 方法注入

大多數應用場景中, 容器中的不少bean都是單例的. 當一個單例的bean須要與另外一個單例bean協做, 或者一個非單例bean須要與另外一個非單例bean協做時, 通常須要經過將一個bean做爲另外一個bean的屬性來處理依賴關係. 當bean的生命週期不一樣時將會發生問題. 假設一個單例bean A須要一個非單例(原型)bean B, 也許每一個方法都有調用. 容器只建立A一次, 所以只有一次機會設置它的屬性. 一旦有用到, 容器不能老是使用B的新實例提供給A.

一種解決方案就是拋棄依賴注入. 你能夠使一個Bean A 經過實現接口ApplicationContextAware被容器所感知, 而且a經過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框架耦合在一塊兒了. 方法注入, 是一種Spring容器更爲高級的特性, 可以讓你更聰明滴處理此類問題.

你能夠從這篇博客中獲取到更多方法注入的動機.

查找方法注入

查找方法注入是重寫容器管理的bean並查找另外一個容器中的命名bean將其返回的能力. 查找通常是一個原型bean, 就像在前面章節討論的. Spring框架經過CGLib庫的二進制代碼動態生成子類重寫方法.

  • 爲了能讓動態的子類工做, 須要爲其生成子類的bean不能是final, 同時須要覆蓋的方法也不能是final.

  • 進行單元測試時, 若是有abstract方法的類須要你本身去定義子類而且提供abstract方法的樁實現.

  • 具體方法也是須要能組件掃描的, 這就須要獲取具體類.

  • 另外一個關鍵的限制查找方法和工廠方法,特別是與配置類中的@bean註解方法不兼容. 這種狀況下, 容器再也不控制建立實例所以也就不能在運行時建立子類.

在前面的CommandManager類的代碼片斷中, Spring容器動態覆蓋實現了createCommand()方法. 類CommandManager沒有任何Spring依賴, 以下所示;

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

在客戶端代碼中包含了須要注入的方法, 這個被注入的方法須要以下的格式:

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

若是方法是abstract修飾的, 子類將實現這個方法, 不然動態生成的子類將重寫定義在源類中的實際方法. 以下所示:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

id定義爲commandManager的bean調用其createCommand()方法, 在其須要myCommand實例的時候. 你必須當心地部署beanmyCommand爲原型bean. 若是它是一個單例, 則每次返回的都是相同的實例.

也能夠使用組件註解的方式, 你能夠經過@Lookup註解在方法上, 以下所示:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或者, 更規範地, 你能夠信任目標bean經過返回的類型解析獲得目標bean.

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

注意一般你須要用一個樁實現來聲明這種帶註釋的查找方法, 這樣他們才能與Spring組件掃描兼容, 默認狀況下, 抽象類會被掃描忽略. 這個限制不適於顯式註冊或顯式導入bean類.

另外一個訪問不一樣做用域的方法是使用ObjectFactory/Provider鏈接點. 參看: 不一樣做用域的bean依賴.

你能夠發現ServiceLocatorFactoryBean(在包org.springframework.beans.factory.config)是頗有用的.

任意方法替換

一種比查找方法注入不那麼有用的形式是在一個管理bean中使用另外一個方法實現去替換方法. 你能夠跳過這節, 直到你須要這種機制再回來看.

使用XML配置的元數據時, 你能夠使用replaced-method元素替換一個已經存在的方法. 請看下面的類定義, 它有個方法computeValue須要被重寫:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

實現接口org.springframework.beans.factory.support.MethodReplacer提供了一個新的方法定義,以下所示:

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
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"/>

你能夠爲元素<replace-method/>須要覆蓋的方法簽名指定一個或多個<arg-type/>元素. 只有類中的方法被重載而且有多個的時候才須要參數簽名. 爲了方便, String類型的參數能夠只是一個縮寫, 例如,下面的寫法都匹配java.lang.String

java.lang.String
String
Str

由於參數的個數常常能區分出可能的選擇, 所以這種縮寫能節省大量輸入時間, 經過一個短字符串來匹配一個參數類型.

1.5 Bean 做用域(scope)

當建立bean定義的時候, 實際上你就有個經過bean定義去建立類真實實例的配方. bean定義是配方的想法是很是重要的, 由於它意味着,你能夠經過一個配方去建立多個實例.

經過bean的定義, 你不只能夠控制插入到對象的依賴和配置值, 還能夠控制經過bean定義的對象的做用域. 這種方式是強大而靈活, 由於你能夠選擇經過配置生成的對象的做用域, 而不是必須在class級別操做對象的做用域. bean能夠定義爲幾個做用域其中的一個. Spring框架支持六種做用域, 四種僅能夠用在web類型的ApplicationContext中. 你也能夠自定義scope.

下面表格描述了支持的做用域:

表 3. Bean 做用域

Scope Description
singleton (默認)對於每一個IoC容器來講, 處理每一個bean定義的對象實例僅有一個
prototype 處理一個bean定義能夠有多個對象實例
request 處理對於每一個HTTP請求僅有一個實例. 也就是對於每一個bean定義, 每一個HTTP請求都有它本身的實例. 僅僅在Web類型的Spring ApplicationContext中是可用的.
session 處理在一個HTTPSession範圍內一個bean的定義. 僅僅在Web類型的Spring ApplicationContext中是可用的.
application 處理在ServletContext級別的bean的定義. 僅僅在Web類型的Spring ApplicationContext中是可用的.
websocket 處理在WebSocket級別的bean的定義, 僅僅在Web類型的Spring ApplicationContext中是可用的.

從Spring3.0開始, 線程級別的做用域是可用的, 但不是默認註冊的. 關於更多請參看SimpleThreadScope的文檔. 關於如何註冊這個做用域或者其餘自定義做用域的指導, 請參看自定義做用域.

1.5.1 單例做用域

容器內僅僅有一個共享的bean實例, 而且全部經過bean定義中的id或者id列表僅能匹配出惟一的特定bean的實例.

換個說法, 當你定義了一個bean,而且將其設置爲singleton做用域時, Spring IoC容器建立了bean定義的惟一實例. 這個惟一實例是存儲在此類單例bean的緩存中的, 而且全部子請求和引用都會返回緩存的bean. 下面的圖展現了單例bean如何工做:
單例bean

Spring單例bean的概念不一樣於GOF模式書中的單例模式. GoF單例硬編碼了對象的做用域, 因此對於每一個ClassLoader,有且僅有一個bean實例被建立. Spring的單例bean做用域對於每一個容器每一個bean來講有且僅有一個. 這意味着, 若是你在每一個容器中定義一個指定的bean, 容器將爲每一個bean的定義生成一個且僅有一個bean的實例. 單例做用域是Spring默認的. 要用XML定義一個單例bean, 你能夠參看下面定義:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

1.5.2 原型做用域

非單例的原型做用域bean將在每次請求的時候建立一個新的實例. 也就是這個bean被注入另外一個bean或者經過容器的getBean()方法調用獲取. 原則上, 使用須要保持狀態的bean時使用原型做用域, 使用狀態無關的bean時使用單例bean.

下圖說明了Spring的單例做用域:

原型bean

(DAO對象不是典型的原型做用域, 由於一個DAO不保持任何會話狀態. 咱們重用單例的說明圖是很容易的)

下面例子定義了XML格式的原型bean

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

同其餘做用域相比, Spring不會管理原型bean的完整生命週期. 容器除了將其實例化,配置,另外還有組裝和將其提供給client外, 不會有更多的原型實例的任何記錄了. 所以, 雖然初始化回調方法由全部做用域的對象都會調用, 但在原型模式來講, 配置的銷燬回調方法不會被調用. 客戶端代碼必須清理原型對象並釋放被其佔用的任何昂貴的資源. 爲了使Spring容器能獲取到原型bean佔用的資源, 嘗試使用自定義的post-處理器, 這個處理器維護這須要被清理的bean引用.

在某些方面, Spring容器對於原型bean的做用是對new操做符的代替. 過去全部的生命週期管理都是客戶端來維護. (關於Spring中bean生命週期的更多信息, 參看:生命週期回調)

1.5.3 擁有原型bean依賴的單例bean

當使用有原型bean作爲依賴的單例bean時, 記住依賴是在實例化的時候解析的. 所以,若是你將一個原型bean注入單例bean, 那這個原型bean是做爲新的bean被初始化了, 而且注入到了單例bean中. 這個原型bean是單例bean的獨佔實例.

儘管如此, 假設你想要在運行期間爲單例bean屢次重複獲取原型bean. 你不能講原型bean注入到單例bean中, 由於注入只發生了一次, 就在容器初始化單例bean並解析他的依賴的時候. 若是你須要一個運行時的原型bean, 參看:方法注入.

1.5.4 Request,Session,Application, 還有webSocket

request,session,application,websocket做用域只有在web類型的Spring ApplicationContext(好比XmlWebApplicationContext)實現中可用.若是在標準的Spring容器中使用這些做用域, 好比ClassPathXmlApplicationContext, 則IllegalStateException將會因未知做用域而拋出.

初始化Web配置

爲了支持bean的這幾個做用域(request,session,application,websocket(web做用域bean)), 在定義bean時還須要作少許的配置. (對於標準的做用域singletonprototype,初始化設置是不須要的)

如何完成初始化設置取決於你特定的Servlet環境.

若是你使用Spring web MVC訪問做用域bean, 實際上,請求是由Spring的DispatcherServlet處理的, 不須要其餘的設置. DispatcherServlet已經暴露了全部的相關狀態.

若是你使用Servlet 2.5 的Web容器, 當不使用DispatcherServlet處理時(好比使用JSF或Struts), 你須要註冊org.springframework.web.context.request.RequestContextListener ServletRequestListener. 對於Servlet 3.0+, 能夠經過接口WebApplicationInitializer編程完成. 或者做爲替換,包括使用舊的容器的話, 在web.xml文件中添加以下聲明:

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

或者, 若是監聽器設置有異常, 考慮使用Spring的RequestContextFilter. filter映射依賴於包含其的web程序配置, 因此你必須合理修改下. 下面列出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都作了一樣的事情, 即綁定Http請求對象到服務請求的Thread. 這使得bean在調用鏈中能夠使用request-和session-做用域.

Request 做用域

參考以下XML配置bean:

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

Spring容器使用loginAction定義爲每一個HTTP請求建立bean LoginAction的實例. 也就是loginActionbean在HTTP請求級別做用域. 你能夠隨意修改實例的內部狀態,由於被loginAction bean定義實例化出來的其餘對象看不到這些修改. 他們是特定於單獨的request的. 當請求完成處理後, request做用域的bean就被拋棄了.

當使用註解驅動的組件或java代碼配置時, @RequestScope註解能用來分配給一個request做用域的組件. 下面例子展現瞭如何使用:

@RequestScope
@Component
public class LoginAction {
    // ...
}
Session 做用域

參看下面XML配置的bean定義:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Spring容器經過userPreferences bean的定義爲每一個HTTP Session生成UserPreferences bean的實例. 換句話說, userPreferences bean 在 HTTP Session 做用域級別. 跟request做用域bean同樣, 你能夠隨意修改實例的內部狀態, 而其餘由同一個userPreferencesbean定義生成的 HTTP Session 的實例不會看到這些變化, 由於他們特定於單獨的 HTTP Session. 當 HTTP Session 最終再也不使用時, 對應的Session做用域的bean也就再也不使用了.

當使用註解驅動組件或java代碼配置時, 你能夠使用@SessionScope註解到session做用域的相關組件上.

Application 做用域

考慮下面XML的bean定義:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Spring容器使用appPreferences bean的定義建立了AppPreferences bean的實例. 也就是appPreferences bean是在ServletContext級別的, 而且做爲一個標準的ServletContext屬性存儲. 這相似於Spring單例bean, 但有兩點重要的不一樣: 它是在每一個Servlet上的單例, 不是在每一個Spring'ApplcationContext'上的單例(可能在任何web程序中有多個),而且實際上它是暴露的而且所以是做爲ServletContext屬性可見的.

當使用註解驅動或者java代碼配置時, 你能夠使用@ApplicationScope註解到application組件上. 以下所示:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
做用域bean做爲依賴

Spring容器不只管理對象(beans)的初始化, 也包含其組裝協做者(依賴項). (舉例來講)若是你想要將一個HTTP request做用域的bean注入到另外一個更長生命週期的bean中, 你能夠選擇注入一個AOP代理對象代替bean. 也就是說, 你須要使用暴露了與做用域對象相同接口的代理對象注入, 這個代理對象可以從相關做用域中獲取到真實對象(例如一個HTTP請求)而且委託調用真實對象上的方法.

在單例bean之間你也能夠使用<aop:scoped-proxy/>, 經過一箇中間代理引用, 這個代理是可序列化的,所以能在反序列化時從新獲取目標單例bean.

當在一個prototype做用域的bean上聲明<aop:scoped-proxy/>時 , 每一個請求共享代理對象的方法將會將其引導到新建實例上並調用.

同時, 在做用域安全的方式下, 從一個短做用域訪問bean, 做用域代理並非惟一的方法. 你也能夠用ObjectFactory<MyTargetBean>聲明你的注入點(也就是,構造器或setter參數或者自動裝配域), 容許經過getObject()在須要對象時獲取當前實例-- 而不是持有其實例或單獨存儲之.

做爲擴展的變體, 你能夠聲明ObjectProvider<MyTargetBean>, 這個對象有幾個訪問方式的變體, 包含getIfAvailablegetIfUnique.

JSR-330 中這被叫作Provider而且用來與Provider<MyTargetBean>一塊兒使用, 而且用get()方法獲取. 關於JSR-330更多請參考這裏.

下面例子中只有一行, 但理解其在背後的緣由比怎麼作更重要:

<?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.something.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.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

爲了建立代理, 須要插入一個子元素<aop:scoped-proxy/>到做用域bean的定義中. (參看建立代理的類型選擇和XML配置方式). 爲何定義在request,session或者自定義做用域上的bean須要<aop:scoped-proxy/>? 考慮下面單例bean的定義並對比上面表述的做用域(注意下面userPreferencesbean定義是不完整的):

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

上面例子中, 單例bean(userManager)注入了HTTP Session-做用域的bean中(userPreferences). 這裏的突出點是userManager是單例的: 它是每一個容器惟一的, 而且它的依賴(在本例中是userPreferencesbean)僅僅注入了一次. 這意味着userManagerbean都是在相同的userPreferences對象上進行操做的(也就是最初被注入的那個).

這不是你想要的結果, 當將一個短生命週期的bean注入到長生命週期的bean中時(例如, 注入一個HTTP Session做用域的協做bean到單例bean中). 相反, 你須要一個單例的userManager對象, 而且對於HTTPsession生命週期, 你須要userPreferences對象並將其指定爲HTTP Session. 所以, 容器建立了一個暴露了與UserPreferences類相同公共接口的對象.(理想狀況下這個對象是UserPreferences實例), 這個對象可以從做用域機制中(HTTP request,session等)獲取到真實的對象. 容器將代理對象注入到userManagerbean, 它並不知道UserPreferences引用是個代理. 本例中, 當UserManager實例調用注入對象UserPreferences的方法時, 實際上它是調用代理的方法. 代理而後獲取從HTTP Session做用域(本例中)的真實UserPreferences對象, 並調用真實對象上的方法.

所以, 你須要下面的配置(完整而正確), 當你注入request-session-做用域的bean時, 以下:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
選擇建立的代理類型

默認狀況下, 當Spring容器用標記爲<aop:scoped-proxy/>元素的bean建立代理時, 一個CGLIB類代理對象就被建立了.

CGLIB代理僅攔截公共調用! 不調用代理上的非公共方法. 他們不會被代理到目標做用域對象上.

或者, 你能夠使用標準JDK基於接口的代理爲做用域bean配置容器. 經過將元素<aop:scoped-proxy>的屬性proxy-target-class設置爲false. 使用JDK基於接口的代理意味着你不須要在classpath下引用多餘的庫. 儘管如此, 這也意味着做用域bean必須實現至少一個接口而且全部注入的的做用域bean必須經過接口引用. 下例展現瞭如何使用接口代理:

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

關於選擇基於類的仍是基於接口的代理, 請參看: 代理機制.

1.5.5 自定義做用域

bean的做用域機制是可擴展的. 你能夠自定義或者從新定義已經存在的做用域, 雖然稍後會知道這不是最佳實踐並且你不能重寫內置的singletonprototype做用域.

建立自定義做用域

爲了集成自定義的做用域到容器中, 你須要實現接口org.springframework.beans.factory.config.Scope, 這個接口將會在本節描述. 對於如何實現自定義做用域的觀念, 參看Spring框架實現的Scope實現還有Scopejava文檔, 裏面解釋了更多須要實現的方法.

Scope接口有四個方法, 用來從做用域獲取,移除,銷燬對象.

以session做用域的實現爲例, 返回session做用域bean(若是不存在,方法在這個實例將在綁定到session後以備未來引用, 而後返回一個新實例).下面的方法從潛在做用域返回對象:

Object get(String name, ObjectFactory objectFactory)

以session做用域的實現爲例, 從潛在的session做用域刪除bean. 對象應該被返回, 但若是指定名稱的bean找不到, 你能夠返回null. 下面的方法將從潛在做用域刪除對象:

Object remove(String name)

下面的方法爲做用域註冊了回調, 將在它被銷燬或者看成用域內的指定對象銷燬時執行:

void registerDestructionCallback(String name, Runnable destructionCallback)

關於銷燬回調, 能夠參看java文檔或者Spring做用域實現的代碼.

下面方法包含了從潛在做用域獲取會話id:

String getConversationId()

這個標識符在每一個做用域都不一樣, 對於session做用域實現, 這個標識符能夠是session標識符.

使用自定義做用域

在你編碼並測試一個或多個自定義做用域實現後, 你須要使Spring容器可以感知到你的做用域. 下面方法是爲Spring容器註冊一個新做用域的核心方法:

void registerScope(String scopeName, Scope scope);

這個方法是在接口ConfigurableBeanFactory聲明的, 在Spring的大多數對於接口ApplicationContext的實現中, 經過BeanFactory屬性能夠獲取到.

registerScope(..)方法的第一個參數是相關做用域的惟一名稱. Spring內置的此類名稱是singletonprototype. 方法的第二個參數是自定義Scope實現的實例, 這個實例就是你想註冊和使用的.

假設你寫好了自定義的Scope實現, 而且像下一個例子同樣註冊到了容器中.

下一個例子使用SimpleThreadScope, 其包含在Spring中但沒有默認註冊. 這個教程與你自定義的scope實現是相同的.

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

你能夠建立bean的定義, 並將自定義的scope實現依附其中. 以下:

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

自定義的Scope實現不只限於使用編程方式註冊, 你也能夠使用聲明方式註冊, 經過CustomScopeConfigurer類進行註冊, 以下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>

當你在FactoryBean實現中使用<aop:scoped-proxy/>時, FactoryBean自身是做用域下的bean, 不是從getObject()方法返回的對象.

1.6 自定義Bean的特性

Spring框架提供了一些接口供你自定義bean的特性. 本節按下面方式組織:

  • 生命週期回調
  • ApplicationContextAwareBeanNameAware
  • 其餘Aware接口

1.6.1 生命週期回調

爲了與容器管理的bean的生命週期交互, 你能夠實現Spring InitializingBeanDisposableBean 接口. 容器會爲前者調用afterPropertiesSet(),爲後者調用destroy() 以使bean執行在bean初始化和銷燬時應該作的操做.

在現代Spring程序中,JSR-250 的 @PostConstruct@PreDestroy 註解被認爲是獲取生命週期回調的最佳實踐. 使用這些註解意味着你的bean不會耦合到Spring特定的接口中. 更多信息請參看:使用@PostConstruct@PreDestroy.

若是你不想用JSR-250註解但仍然想去除耦合, 考慮元數據中使用 init-methoddestroy-method.

在內部, Spring框架使用接口BeanPostProcessor的實現來處理他發現的任何回調接口而且調用合適的方法. 若是你須要自定義特性或者修改Spring沒有默認提供的其餘生命週期行爲, 你能夠實現BeanPostPocessor. 更多信息請參看:容器擴展點.

除了初始化和銷燬回調外, Spring管理的對象也能夠實現Lifecycle接口, 以便那些對象能參與容器自身生命週期驅動的啓動和中止過程.

生命週期回調接口將在本節描述.

初始化回調

org.springframework.beans.factory.InitializingBean 接口可以在容器設置了bean的全部必要屬性後執行初始化工做. InitializingBean 接口只有一個方法:

void afterPropertiesSet() throws Exception;

咱們強烈建議不要使用InitializingBean接口,由於它沒必要要地耦合到了Spring中. 相反, 咱們建議使用@PostConstruct 註解或者指定一個POJO 初始化方法. 若是使用XML配置, 你能夠使用init-method屬性指定一個沒有參數的方法名稱. Java代碼配置的話, 你能夠使用@Bean上的 initMethod 屬性. 參看:獲取生命週期回調. 參考下例:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

上面的例子和下面的例子(由兩個清單組成)有相同的效果:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

這樣,上面的頭兩個例子代碼沒有與Spring耦合.

銷燬回調

實現org.springframework.beans.factory.DisposableBean接口使得一個bean能在包含它的容器銷燬時回調. DisposableBean接口只有一個方法:

void destroy() throws Exception;

咱們建議你不要使用DisposableBean回調接口, 由於代碼沒必要要的耦合到Spring了. 相反, 咱們建議使用@PreDestroy註解, 或者在bean定義中指定一個普通方法. 使用XML配置的話你能夠使用元素<bean/>上的destroy-method屬性. 使用java代碼配置的話, 你能夠使用@Bean註解上的destroyMethod屬性. 參看獲取生命週期回調. 示例以下:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

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

上面的定義等同於下面的定義:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

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

上面的兩個定義與Spring代碼沒有耦合.

你能夠爲<bean>元素分配給屬性destroy-method一個專門的值(inferred(推斷)), 這個值指示Spring自動去探測特定bean上的公共closeshutdown方法. (任何實現了java.lang.AutoCloseablejava.oi.Closeable的類將會匹配到) 你也能夠爲<beans>元素分配給屬性default-destroy-method一個特定值, 用來在全部bean上應用動做(參看默認初始化和銷燬方法). 注意, 這個java代碼配置的默認行爲.

默認初始化和銷燬方法

當你不用Spring特定的InitializingBeanDisposableBean 接口回調的初始化和銷燬方法時, 你通常編寫方法的名稱相似init(),initialize(),dispose()等. 理論上, 這些生命週期回調方法是標準化的貫穿於整個項目, 這樣開發人員就能使用相同的方法並保持一慣性.

你能夠配置Spring容器查找每一個bean上的命名初始化和銷燬方法. 這意味着你做爲開發人員能編寫程序類並使用名爲init()的初始化方法回調, 而並不須要爲每一個bean定義上指定init-method="init". Spring容器在bean被建立時調用這個方法(並遵循前面描述的標準生命週期回調). 這個特性也須要強制執行一向的初始化和銷燬方法回調的命名約定.

假設你的初始化回調方法叫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.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

頂級元素<beans/>上的屬性default-init-method促使Spring容器去識別在bean上的init()方法做爲初始化回調函數. 當bean被建立和組裝後, 若是bean有這麼個方法, 它就將在恰當的時候執行.

你也能夠在頂級元素<beans/>上使用default-destroy-method, 相似地去配置(XML)銷燬回調函數.

在已經存在的bean上, 已經有按約定命名的回調方法, 你能夠使用<bean/>元素上指定init-methoddestroy-method重寫默認的方法.

Spring容器擔保在bean的依賴所有被提供後可以當即調用配置的初始化回調. 所以,初始化回調發生在原始bean引用上, 這意味着AOP注入尚未應用到bean上. 一個完整的目標bean是首先被建立而後一個AOP代理注入鏈被引用. 若是目標bean和代理被單獨定義, 你的代碼就可以繞開代理與目標bean交互. 所以, 在init方法上應用注入是不合邏輯的, 由於這樣將耦合目標bean的生命週期到它的代理或注入器, 而且當你的代碼與原始的目標bean直接交互時留下奇怪的語義.

組合生命週期機制

從Spring2.5開始, 控制bean生命週期行爲有三種選擇:

  • 回調接口 InitializingBeanDisposableBean
  • 自定義的init()destroy() 方法
  • 註解 @PostConstruct@PreDestroy. 你能夠組合這些機制去控制bean.

若是給一個bean配置了多種生命週期機制,而且每一個機制都使用了不一樣的方法名, 那麼每一個配置方法將會按本提示後面給出的順序執行. 儘管如此, 若是爲多個機制配置了相同的方法名-- 例如, 初始化方法的init()-- 那該方法將只執行一次, 就如上面所闡述的.

爲同一個bean配置多個生命週期機制, 初始化方法將按下面順序執行:

  • 使用註解@PostConstruct的方法
  • 經過回調接口InitializingBean定義的afterPropertiesSet()方法
  • 自定義的init()方法

銷燬方法按相同的順序執行:

  • 使用註解@PreDestroy的方法
  • 經過回調接口DisposableBean定義的destroy()
  • 自定義的destroy()
啓動和中止回調

對於擁有本身生命週期須要的任何對象, 接口Lifecycle定義了必不可少的方法(例如啓動和中止某些後臺進程).

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何Spring管理的對象均可以實現接口Lifecycle. 而後, 當ApplicationContext自身收到開始和中止信號時(例如,運行時的中止/重啓場景), 它將級聯調用上下文定義的全部Lifecycle實現. 這是經過委託給LifecycleProcessor完成的, 以下所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

注意, LifecycleProcessor自己是接口Lifecycle的擴展. 它添加了兩個方法在上下文刷新和關閉時交互.

注意, 標準的org.springframework.context.Lifecycle接口是一個明確的規約, 是爲了啓動和中止通知使用, 並無在上下文刷新時應用自動啓動. 對於特定bean的自動啓動更細粒度的控制(包括啓動階段), 考慮實現接口org.springframework.context.SmartLifecycle代替.

同時請注意, 中止通知不能保證必定在銷燬時會發生. 在標準的中止過程下,全部的Lifecycle bean在通常的銷燬回調被傳播前首先接收一箇中止通知. 儘管這樣, 在上下文生命週期熱刷新時或者在拋棄刷新嘗試時, 只有銷燬方法會被調用.

啓動和中止調用的順序多是比較重要的. 若是兩個對象間存在依賴關係, 依賴方將在被依賴方以後啓動, 而且在被依賴方以後中止. 不過有時候直接依賴是不知道的. 你可能僅僅知道一些類型的對象的優先於另外一些類型的對象啓動. 這種狀況下, SmartLIfecycle接口定義了另外一種選項, 在其父類接口Phased上指定getPhase()方法. 下面的代碼展現了Phased接口:

public interface Phased {

    int getPhase();
}

下面展現了SmartLifecycle接口:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

當啓動時, 底層階段的對象優先啓動. 當中止時, 順序相反. 所以, 一個實現了接口SmartLifecycle而且其方法getPhase()返回Integer.MIN_VALUE將會在第一個啓動的, 並是最後一箇中止的. 在此範圍的另外一端,也就是Integer.MAX_VALUE將代表對象是最後啓動的而且是最早中止的(可能由於它依賴於要運行的其餘進程). 當提到階段值的時候, 重要的一點是任何"正常"Lifecycle對象若是沒有實現SmartLifecycle接口的話,這個階段值的默認爲0. 所以, 任何負值的對象將在那些標準組件以前啓動(在其後中止). 若是正值則相反.

經過SmartLifecycle接收一箇中止方法. 任何實現必須在對象的中止過程完成後調用回調的run()方法. 這就使程序擁有了異步中止能力, 由於接口LifecycleProcessor的默認實現DefaultLifecycleProcessor在每一個階段中等待該組對象的超時值以調用該回調. 每一個階段的超時時間是30秒. 你能夠經過定義一個名爲lifecycleProcessor的bean來重寫默認的生命週期處理器. 若是你想僅僅是修改超時時間, 定義以下:

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

就像前面提到的, LifecycleProcessor接口也定義了上下文刷新和關閉的回調方法. 後者驅動關閉過程,就好像stop()方法被顯式調用, 但它發生在上下文關閉時. 另外一方面, 刷新回調賦予了SmartLifecyclebean的另外一種特性. 當上下文被刷新(在全部對象建立和初始化後), 回調就被調用了. 此時, 默認的生命週期處理器檢查每一個SmartLifecycle對象的isAutoStartup()方法返回的bool值. 若是是true, 對象將馬上啓動而不是等到上下文或者其自身的start()方法顯式調用(與上下文刷新不一樣,上下文啓動並非標準上下文實現的標準實現). phase值和任何依賴關係決定了前面描述的啓動順序.

在非WEB程序中優雅地中止Spring容器

這節內容僅僅應用於非web程序. Spring 基於web的ApplicationContext實現已經提供了當web程序關閉時優雅地關閉容器的代碼.

若是你在一個非web程序中(例如, 一個胖客戶端桌面環境)使用Spring容器, 註冊一個JVM的中止鉤子. 這樣作能夠保證你優雅地關閉, 並調用單例bean上關聯的銷燬方法使全部資源可以釋放. 你必須依然要正確配置和實現那些銷燬回調.

爲了註冊中止的鉤子, 調用接口ConfigurableApplicationContext聲明的方法registerShutdownHook(), 以下所示:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

1.6.2 ApplicationContextAwareBeanNameAware

ApplicationContext建立了一個實現了org.springframework.context.ApplicationContextAware接口的對象實例時, 這個實例就提供了對ApplicationContext的引用. 下面代碼是接口ApplicationContextAware的定義:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

所以, bean可以經過編程方式操做建立了他們的ApplicationContext, 經過ApplicationContext接口或經過能轉化爲這個接口子類型的對象(例如: ConfigurableApplicationContext,這個接口暴露了更多的功能). 其中一個用途是對其餘bean進行編程檢索. 有時候這種能力是有用的. 但整體上你應該避免使用, 由於它會耦合你的代碼到Spring而且不能遵循IoC風格, 在那裏協做bean做爲bean的屬性. ApplicationContext的其餘方法提供了訪問文件,發佈程序事件,還有訪問MessageSource等功能. 這些附加特性參見ApplicationContext的附加功能.

自從Spring2.5開始, 自動裝配是另外一種能獲取ApplicationContext引用的替代方式. "傳統的"constructorbyType裝配模型(如同在裝配協做者一節描述的)能分別爲構造器參數或setter方法參數提供ApplicationContext類型依賴. 更多的靈活性, 包括自動裝配字段和多參數方法, 使用新的基於註解的裝配特性. 若是你這麼作了, 帶有@Autowired註解的域, 構造函數或方法, 那ApplicationContext就自動裝配到一個指望ApplicationContext類型的屬性域,構造函數參數, 或者方法參數中去了. 更多信息請參見使用@Autowired.

ApplicationContext建立了實現org.springframework.beans.factory.BeanNameAware接口的類實例後, 這個類就擁有了在定義時爲其指定的名字引用. 下面代碼展現了接口BeanNameAware的定義:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回調在bean的屬性被佈局後可是在InitializingBean, afterPropertiesSet或自定義初始化方法以前被調用.

1.6.3 其餘Aware接口

除了(以前討論的)ApplicationContextAwareBeanNameAware外, Spring提供了一系列Aware接口以使bean向容器代表他們須要特定的基礎設施依賴. 做爲廣泛法則, 這個名稱代表了依賴類型. 下面表格簡要列出最重要的Aware接口:

表4 Aware接口

名稱 注入的依賴 解釋文檔索引
ApplicationContextAware 聲明ApplicationContext ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAware 封裝的ApplicationContext的事件發佈 ApplicationContext的附加功能
BeanClassLoaderAware 用來加載bean的類加載器 實例化bean
BeanFactoryAware 聲明BeanFactory ApplicationContextAware and BeanNameAware
BeanNameAware 聲明的bean的名稱 ApplicationContextAware and BeanNameAware
BootstrapContextAware 容器運行其中的資源適配器BootstrapContext, 通常僅JCA可用的ApplicationContext中可用
LoadTimeWeaverAware 加載時爲處理類定義的加載器 Spring框架中使用AspectJ加載時織入
MessageSourceAware 配置的解析消息的資源策略(支持參數化和國際化) ApplicationContext的附加功能
NotificationPublisherAware Spring JMX 提醒發佈者 提醒
ResourceLoaderAware 底層訪問資源的配置加載器 Resources
ServletConfigAware 容器運行其中的當前ServletConfig,僅在web-aware的Spring ApplicationContext中可用 Spring MVC
ServletContextAware 容器運行其中的當前ServletContext,僅在web-aware的Spring ApplicationContext中可用 Spring MVC

請再次注意, 使用這些接口將使你的代碼耦合到Spring API, 而且也不遵循IoC風格. 所以咱們建議他們當須要訪問容器時使用基礎設施bean.

1.7 Bean定義繼承

bean的定義包含不少配置信息, 包含構造函數參數, 屬性值, 還有特定於容器的信息, 例如初始化方法,靜態工廠方法等. 一個bean的子類定義繼承了父定義中的配置數據. 子類定義能夠覆蓋或者按需添加數據. 使用父子定義能夠節省不少編碼. 事實上, 這是一種模型.

若是你使用編程方式使用ApplicationContext接口, 子類bean的定義是經過ChildBeanDefinition類來表現的. 許多用戶不在這個層面使用它. 相反, 他們經過在諸如ClassPathXmlApplicationContext的類中生命配置bean. 當你使用基於XML的配置元數據時, 你能夠經過parent屬性指明子類bean定義, 其值是父bean的定義名稱. 下例展現瞭如何這麼作:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<!--注意`parent`屬性-->
<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

若是沒有指定,子類bean定義將使用父類定義的bean類,但依然能夠覆蓋它. 後一種狀況下, 子類必須兼容父類(也就是必須接受父類的屬性值).

子類繼承了父bean的做用域,構造函數參數值, 屬性值還有方法重寫, 同時能夠添加新的值給它. 任何你指定的做用域,初始化方法,銷燬方法或者static工廠方法配置都將覆蓋掉父類的配置.

剩下的配置老是從子類定義獲取: 依賴,裝配模式,依賴檢查,單例, 懶加載.

上面例子中父類定義使用abstract屬性顯式標記父bean是抽象的. 若是父bean不指定類型, 顯式標記父bean爲abstract是必須的, 以下所示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

父bean不能被實例化本身, 由於它不完整, 且顯式標記爲abstract了. 當一個定義是abstract的時, 它僅僅是爲子定義提供的一個純淨的模板bean定義. 視圖嘗試單獨使用這個抽象bean, 或者將它引用爲另外一個bean的ref屬性, 或者使用父bean的id執行getBean()方法將返回錯誤. 類似的, 容器內部的preInstantiateSingletons()方法將忽略定義爲抽象的bean.

ApplicationContext默認預初始化全部的單例. 所以, 重要的一點是(至少對於單例bean)若是你有個(父)bean定義, 而且僅僅想把它做爲模板使用, 而且給他指定了類名, 你就必須保證設置了abstract屬性爲true, 不然應用程序上下文將(嘗試)預實例化這個abstractbean.

1.8 容器擴展點

一般, 攻城獅不須要ApplicationContext的子類實現. 相反, Spring容器能夠經過實現特定接口的方式擴展. 如下幾節描述了這些接口.

1.8.1 使用BeanPostProcessor自定義bean

BeanPostProcessor接口定義了一些回調方法, 你能夠實現提供你本身的(或者覆蓋默認的)初始化邏輯, 依賴解析邏輯等. 若是你想要在容器完成初始化, 配置和實例化bean以後實現一些本身的邏輯, 你能夠插入一個或多個BeanPostProcessor的實現.

你能夠配置多個BeanPostProcessor實例, 並可以經過order屬性的值來控制這些實例執行的順序.若是BeanPostProcessor實現了Ordered接口, 你就能夠設置這個屬性了. 若是你寫了本身的BeanPostProcessor, 你就也應該考慮實現Ordered接口. 更多細節可參看javadoc關於BeanPostProcessorOrdered接口的部分. 也能夠參看: 編程註冊BeanPostProcessor的實例.

BeanPostProcessor實例做用在bean或對象上. 也就是Spring容器實例化一個bean實例後BeanPostProcessor實例開始作他們的工做.

BeanPostProcessor是容器級別的做用域. 僅僅是你使用容器層次結構的時候纔是有意義的. 若是你在容器中定義了一個BeanPostProcessor,它將僅僅處理這個容器中的bean. 換句話說, 定義在一個容器中的bean不能被其餘容器中的BeanPostProcessor處理, 就算這倆容器都是相同等級層次架構的一部分.

爲了改變bean的定義(也就是定義bean的藍圖), 你須要使用BeanFactoryPostProcessor, 這在使用BeanPostProcessor來自定義配置原數據中有描述.

org.springframework.beans.factory.config.BeanPostProcessor接口包含兩個回調方法. 當一個post-processor的bean被註冊到容器後, 對於每一個本容器實例化出的bean, post-processor都將從容器獲取到回調方法, 是在容器初始化方法以前(例如InitializingBean.afterPropertiesSet()或者任何聲明爲init的方法)將被調用, 且在bean實例化回調方法以後. post-processor能夠作任何事情, 包括徹底忽略回調方法. 一個bean post-processor一般檢查回調接口, 或者使用代理來包裝bean. 一些Spring AOP 基礎設施類就是實現爲post-processor, 爲了提供代理包裝邏輯.

ApplicationContext自動探測在配置元數據中任何實現了BeanPostProcessor接口的bean. ApplicationContext將做爲post-processor註冊了這些bean, 以便晚些時候在bean建立的時候調用. bean post-processor可以以和其餘bean相同的方式部署到容器中.

注意當在配置類上使用@Bean註解的工廠方法聲明BeanPostProcessor時, 工廠方法的返回類型必須是實現類型自己, 或者至少是接口org.springframework.beans.factory.config.BeanPostProcessor, 明確表示bean的post-processor屬性. 不然, ApplicationContext不能在徹底建立它時經過類型自動探測. 由於BeanPostProcessor須要被早點實例化, 從而被用於其餘bean的初始化過程當中, 因此這種早期探測是相當重要的.

編程的方式註冊BeanPostProcessor實例

建議註冊BeanPostProcessor的方式就是前面提到的,經過ApplicationContext自動檢測, 同時你也能夠用編程的方式使用ConfigurableBeanFactory的方法addBeanPostProcessor()進行註冊. 當你須要評估註冊前的條件邏輯, 或者想要在一個層次結構中跨上下文拷貝post-processor時, 編程的方式就頗有用了. 注意, 編程添加的BeanPostProcessor實例並不關心Ordered接口. 這裏, 是註冊的順序決定了執行的順序. 同時注意, 編程註冊的BeanPostProcessor接口老是在那些自動檢測註冊的接口以前處理, 就算顯式指定了順序也是如此.

BeanPostProcessor實例和AOP自動代理

實現了BeanPostProcessor接口的類是特殊的而且被容器區別對待. 全部BeanPostProcessor實例和他們直接飲用的bean都是在啓動時實例化的, 做爲ApplicationContext啓動階段的一個特殊部分. 接下來, 全部BeanPostProcessor實例將被以一種風格註冊並應用到全部容器的bean上. 由於AOP自動代理是做爲BeanPostProcessor自身的一個實現. 因此BeanpostProcessor實例和它們直接引用的bean都不符合自動代理的條件,所以,它們中沒有織入的方面。

對於這樣的bean, 你可能看到以下日誌信息: Bean someBean 不符合全部BeanPostProcessor接口處理的條件(例如:不符合自動代理).

若是你有經過自動裝配或@Resource註解(可能會回到自動裝配)方式裝配bean到BeanPostProcessor中, Spring在查找類型匹配的依賴候選時, 可能會訪問到不指望的bean, 使得他們不符合自動代理或者其餘bean post-processor處理. 例如, 若是你有個@Resource的依賴項, 其中字段或setter名稱並不直接對應到bean的聲明名稱, 而且不使用name屬性, 那麼Spring將按類型訪問其餘bean來匹配它們.

下面的例子展現了在ApplicationContext中如何編碼, 註冊和使用BeanPostProcessor.

示例: BeanPostProcessor-風格的Hello World程序

第一個例子是基本用法. 展現了一個自定義的BeanPostProcessor實現調用每一個容器建立的bean的toString()方法並將結果打印到控制檯.

下面是代碼:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

下面beans元素使用了InstantiationTracingBeanPostProcessor:

<?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, 它可以依賴注入給任何其餘bean. (上面配置定義了個bean, 是使用Groovy腳本的形式,Spring動態語言支持的細節請看:動態語言支持)

下面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
示例: RequiredAnnotationBeanPostProcessor

使用回調接口或註解與自定義BeanPostProcessor實現合力是擴展Spring容器的常見作法. 一個例子是Spring的RequiredAnnotationBeanPostProcessor, 一個BeanPostProcessor的實現, 它是隨Spring發行附帶的, 能確保標記爲任意註釋的beans上的javabean屬性是實際上配置爲依賴注入的一個值.

1.8.2 使用BeanFactoryPostProcessor自定義配置元數據

下一個擴展點咱們看一下org.springframework.beans.factory.config.BeanFactoryPostProcessor. 這個接口的語法相似於其餘的BeanPostProcessor, 但有個主要不一樣: BeanFactoryPostProcessor操做bean的元數據. 也就是, Spring容器使用BeanFactoryPostProcessor讀取配置元數據並可能在容器實例化任何除BeanFactoryPostProcessor以外的bean以前改變它.

你能夠配置多個BeanFactoryPostProcessor實例, 並能夠經過order屬性控制這些實例的執行順序. 雖然,你僅僅須要設置實現了Ordered接口的BeanFactoryPostProcessor的這個屬性. 若是你編寫了本身的BeanFactoryPostProcessor, 你就應該考慮實現Ordered接口. 更多細節參看:BeanFactoryPostProcessorOrdered的java文檔.

若是你想修改實際的bean實例(也就是經過配置元數據建立的對象), 那你就須要使用BeanPostProcessor(以前章節討論的經過BeanPostProcessor自定義bean). 雖然技術上能夠使用BeanFactoryPostProcessor與bean實例協做(例如, 經過使用BeanFactory.getBean()), 但這樣作將致使早產的bean實例, 侵犯了標準的Spring容器生命週期.這樣容易致使反作用, 例如繞開bean處理.

同時BeanFactoryPostProcessor實例是容器級別的做用域. 這僅僅在使用容器層次結構的時候是有意義的. 若是你容器中定義了一個BeanFactoryPostProcessor, 那他就只在這個容器定義的bean起做用.在一個容器中的bean定義不能被另外一個容器中的BeanFactoryPostProcessor處理, 就算兩個容器都是相同層次結構的一部分.

一個bean工廠的post-processor, 當它在ApplicationContext中聲明時自動執行, 爲了給定義在容器中的元數據作出修改. Spring包含不少預約義的工廠post-processor, 例如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer. 你也能夠使用自定義的BeanFactoryPostProcessor--例如, 註冊自動以的屬性編輯器.

ApplicationContext自動檢測那些部署到其中的, 實現接口BeanFactoryPostProcessor的bean. 在恰當的時候, 它使用這些bean爲bean工廠的post-processor. 你能夠象其餘bean同樣部署這些post-processor bean.

與使用BeanPostProcessor同樣, 你一般不想要配置BeanFactoryPostProcessor爲延遲加載. 若是沒有其餘bean引用一個Bean(Factory)PostProcessor, 那post-processor將不會被初始化. 所以, 將其標記爲延遲初始化是會被忽略的, 而且Bean(Factory)PostProcessor將會被更早的初始化, 就算你設置了<beans/>元素的default-lazy-init=true.

例子: 類的名字替換PropertySourcesPlaceholderConfigurer

經過使用標準的java Properties 格式, 你能夠使用PropertySourcesPlaceholderConfigurer來擴展獨立在文件中的bean定義中的屬性值. 這麼作能令人們部署程序去自定義的特定環境的屬性, 例如數據庫URLs和密碼, 而不須要複雜的有風險的去修改主要的XML定義文件.

請看下面的XML格式的配置元數據片斷, 裏面有個使用佔位符的DataSource值被定義:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/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>

示例展現了從一個外部Properties文件中配置的屬性. 在運行時, PropertySourcesPlaceholderConfigurer被用來替換數據源中的一些屬性值. 須要替換的屬性是以一種${properties-name}的格式指定, 遵循Ant和log4j以及JSP EL風格.

真正的值來自另外一個標準的Properties文件:

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

所以, 字符串${jdbc.username}在運行時被'sa'替換, 而且其餘的與鍵匹配的佔位符值都給替換了. PropertySourcesPlaceholderConfigurer檢查properties佔位符和bean的定義屬性. 並且, 你能夠自定義佔位符前綴和後綴.

隨着Spring2.5版本的context命名空間, 你能夠使用專門的配置元素去配置屬性佔位符. 你能夠爲location屬性提供一個或多個用逗號分隔的值列表. 以下:

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

PropertySourcesPlaceholderConfigurer不只查找指定的properties文件. 默認狀況下, 若是它不能從指定的屬性文件中找到, 它會繼續查找SpringEnvironment屬性和標準的System屬性:

你能夠使用PropertySourcesPlaceholderConfigurer去替換類的名稱, 這有時候是有用的, 當你不得不在運行時選擇一個特定的類實現的時候. 下面展現了這種狀況:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

若是這個類不能在運行時被解析爲一個有效的類, 解析失敗發生在當它將要被建立時, 對於一個非延遲加載的bean, 也就是在ApplicationContextpreInstantiateSingletons()階段.

示例: PropertyOverrideConfigurer

PropertyOverrideConfigurer是另外一個bean工廠post-processor, 相似於PropertySourcesPlaceholderConfigurer, 但又不一樣於後者, 對於bean的properties, 最初的定義能夠沒有默認值或者壓根就沒值. 若是一個覆蓋的Properties文件沒有bean的properties合適的入口, 那麼默認的上下文定義將被使用.

注意, bean的定義並不知道被覆蓋, 因此覆蓋配置被使用的xml定義文件並非當即就可見. 萬一有多個PropertyOverrideConfigurer實例爲同一屬性定義了多個, 那依據覆蓋機制最後一個將獲勝.

properties文件配置的行格式以下:

beanName.property=value

下面是這種格式的示例:

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

示例文件能被定義了一個名字爲dataSource的bean使用, 其中包含了driverurl屬性.

組合屬性名稱也是受支持的, 只要路徑path的每一個被複寫的部分都是非空, 最終屬性除外(假設是使用構造器初始化的). 在接下來的例子中, beantomfred屬性的bob屬性的sammy屬性被設置爲123.

tom.fred.bob.sammy=123

指定的覆寫的值老是字面量. 他們不會被解析爲bean的引用. 當xml bean定義中指定一個bean的引用時,這個約定一樣被使用.

自從Spring 2.5的context命名空間開始, 就能夠使用一個明確的配置元素去配置屬性了. 以下所示:

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

1.8.3 使用FactoryBean自定義實例化邏輯

你能夠爲自己是工廠的類實現接口org.springframework.beans.factory.FactoryBean.

FactoryBean接口是可插入Spring容器實例化邏輯的一個點. 若是你有比較複雜的代碼, 這種代碼使用java比使用一對xml文件更好的表達, 你就能夠本身去建立本身的FactoryBean, 將複雜的實例化代碼寫進這個類, 並將這個類插入到容器中.

FactoryBean接口提供了額三個方法:

  • Object getObject(): 返回工廠建立的實例對象. 這個實例多是共享的, 取決於工廠返回的是單例仍是原型.

  • boolean isSingleton(): 若是這個FactoryBean返回單例就是true,不然是false.

  • Class getObjectType(): 返回對象的類型, 這個對象是getObject()方法返回的. 或者返回null, 若是類型不能預知.

FactoryBean的觀念和接口在Spring框架中多處使用. Spring自身提供的就總有50多個實現.

當你須要問容器請求接口FactoryBean的真實實例自己, 而不是它生產的bean時, 在調用ApplicationContext的方法getBean()方法將返回在bean的id前面冠以&. 因此, 對於一個id爲myBeanFactoryBean, 調用getBean("myBean")將返回FactoryBean的產品, 而調用getBean("&myBean")返回FactoryBean實例自己.

1.9 基於註解的容器配置

Spring中是否註解配置要優於XML的配置

基於註解的配置是否優於XML, 關於註解的介紹引出了這個問題. 簡短的答案是"看狀況". 長一點的答案是:每種方式都有其優缺點, 常常地, 這取決於開發人員決定哪一種方式更適合他們. 基於他們定義的方式, 註解提供了許多上下文, 這致使了更短而更閉聯的配置. 而XML更精通於不接觸代碼或無需從新編譯而組裝組件.一些開發人員更喜歡將裝配接近源碼的方式, 而其餘人則認爲註解的類再也不是POJO了, 並且配置變得零散而難以控制.

無論何種選擇, Spring都能兼容兩種方式甚至混用它們. 值得指出,經過java配置選項, Spring使得註解在一個非侵入方式下使用, 不須要觸碰目標組件的源碼, 至於工具, 全部的配置方式都被Spring Tool Suite 支持.

一種替換XML配置的方式是基於註解的配置, 後者依賴於二進制字節碼原數據裝配組件而不是使用尖括號的聲明. 不一樣於XML描述裝配, 開發人員將配置轉移到組件類內部,經過註解在類上, 方法上或域字段上. 就像在示例:RequiredAnnotationBeanPostProcessor中同樣, 使用BeanPostProcessor與註解協做是一種擴展Spring 容器的廣泛方法. 例如, Spring2.0賦予了使用@Required註解強制必填屬性的能力. Spring2.5使得使用相同的通常模式來驅動Spring DI 成爲可能. 本質上, @Autowired註解提供了在自動裝配一節的描述相同的能力, 但擁有更細粒度的控制和更廣的適用性. Spring2.5一樣添加了JSR-250註解的支持,例如@PostConstruct@PreDestroy. Spring 3.0 添加了對JSR-330(java的依賴注入)註解支持, 包含在javax.inject包下如@Inject@Named. 這些註解的細節能夠在相關章節找到.

註解注入在XML注入以前執行. 所以, XML配置將覆蓋了經過兩種方式裝配的註解.

一般, 你能夠像分離的bean定義同樣註冊他們, 但你也能夠隱式的經過包含在XML配置中的下面標籤來註冊. (注意將context命名空間包含進去):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

(隱式註冊的post-processor包含AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, 還有前面提到的RequiredAnnotationBeanPostProcessor.)

<context:annotation-config/>僅僅查找相同應用上下文定義的bean上的註解. 這意味着若是你爲一個DispatcherServletWebApplicationContext上添加標籤<context:annotation-config/>, 它僅僅在你的Controller上檢查@Autowired, 而不會在service上. 可參看:DispatcherServlet獲取更多信息.

1.9.1 @Required

@Required註解引用在bean的setter方法上, 以下:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

這個註解代表, 受影響的bean的屬性必須在配置時就肯定, 無論是經過bean定義中的顯式的屬性值仍是自動裝配. 若是沒有找到值,容器將拋出異常. 這容許早期和顯式失敗, 避免了NullPointerException等諸如此類的實例. 咱們依然建議你將斷言放置在bean類內部(例如, 放在初始化方法裏面). 這樣就強制了必填屬性的引用和值, 就算你在容器外使用類也是如此.

@Required註解在Spring framework 5.1開始起被否認了, 更傾向於必填設置經過構造函數(或使用隨bean屬性的setter方法InitializingBean.afterPropertiesSet()的自定義實現) .

1.9.2 使用@Autowired

JSR 330 的註解@Inject 可以替換在本節例子中使用的Spring註解@Autowired. 這裏(bean的標準註解)有更多細節.

你能夠將@Autowired註解應用到構造函數, 以下所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

從Spring 框架4.3 開始, 若是目標bean只定義了一個構造函數, 那麼@Autowired註解就不是必須的. 但若是有多個構造函數, 那至少須要註解其中的一個來告訴容器使用哪一個.

你也能夠將@Autowired註解應用到傳統的setter方法上, 以下:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

你也能夠將註解應用到具備任意名稱和多個參數的方法, 以下:

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 {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

確保你的目標組件(如MovieCatalogCustomerPreferenceDao)和使用@Autowired註解的地方是類型一致的. 不然,注入可能會由於運行時沒有匹配到類型而失敗.

對於XML定義的bean或組件類經過classpath掃描, 容器一般能夠預知類型. 但對於@Bean工廠方法, 你須要確保返回類型要足夠明確. 對於那些實現了多個接口的組件, 或隱式的引用到具體實現類型的組件, 考慮在工廠方法返回值將其聲明爲特定類型(至少要指定爲與注入點引用的bean須要的類型).

你也能夠爲須要一個數組的域或方法提供一組特定類型的bean, 這些bean從ApplicationContext中獲取. 以下:

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註釋. 不然他們的順序遵循容器中定義的相關目標bean註冊的順序.

你能夠在目標類級別上聲明@Order註解, 以及在@Bean方法上, 這多是單獨的bean定義(在使用同一個bean類的多個定義的狀況下). @Order值可能影響注入點的優先級, 但要知道, 它不影響單例的啓動順序, 而這是由依賴關係和@DependsOn聲明決定的正交關係.

注意標準的javax.annotation.Priority註解不能在@Bean級別上使用, 由於它不能聲明在方法上. 它的語義能夠經過@Order值與每一個類型的單一bean上使用@Primary相結合來建模.

甚至類型化的Map實例也是能夠自動包裝的, 只要期待的key值是String類型. Map的值包含了全部指望類型的實例, 而key值包含了相關bean的名稱, 以下所示:

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

    // ...
}

每一個類僅有一個構造函數能夠被標記爲必填, 但能夠有多個註解的構造函數. 那種狀況下, 每一個都將被做爲候選, 而且Spring使用最大參數的構造函數, 這個構造函數能夠知足依賴.

建議將@Autowired所需的必填屬性置於@Required註解之上. 必填屬性代表屬性不是爲了自動裝配必需要的. 屬性將被忽略若是它不能被自動裝配. 而@Required, 另外一方面它更強, 它能夠以容器支持的任何方式設置屬性. 若是沒有值注入, 相關的異常將會拋出.

做爲替換, 你能夠使用java8的java.util.Optional來表達一個非必填屬性依賴. 以下所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

從Spring 5.0 開始, 你也能夠使用@Nullable註解了(任何包中任何類型的註釋, 例如JSR-305的:javax.annotation.Nullable) :

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

您也能夠爲衆所周知的依賴項接口使用@Autowired: BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher,還有MessageSource. 這些接口和他們的擴展接口, 例如:ConfigurableApplicationContext或者ResourcePatternResolver, 會被自動解析, 不須要特殊的設置步驟. 下面例子展現了自動裝配一個ApplicationContext對象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired, @Inject, @Resource, 和 @Value 是被Spring的BeanPostProcessor實現所處理. 這意味着你不能將這些註釋應用到你本身的BeanPostProcessor或者BeanFactoryPostProcessor類型. 這些類型必須顯式的使用XML或Spring@bean方法裝配.

1.9.3 使用@Primary註釋進行微調

由於按類型的自動裝配可能會有多個候選, 因此有必要在選擇過程當中有更多控制. 一種達成此目標的方式是使用@Primary註解. @Primary代表當多個bean候選都符合單例依賴時, 這個特定的bean應當被自動裝配給引用. 若是確實有一個主要的bean, 那它就成爲了自動裝配的值.

參看下面的配置, 定義了firstMovieCatalog做爲主要的MovieCatalog:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

使用上面的配置, 下面的MovieRecommender被自動裝配了firstMovieCatalog:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

相關的bean定義以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

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

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

1.9.4 使用Qualifiers微調

當使用按類型自動封裝時, 若是有幾個不一樣的實例而只有一個主要的實例被肯定, @Primary是一種有效的方法.當你須要在選擇過程當中有更多控制時, 能夠使用Spring的@Qualifier註解. 你能夠使用它的幾個相關參數來縮小類型匹配範圍, 這樣對於任一參數選擇其特定的bean. 在最簡單的示例中, 它能夠是一個普通的描述值, 以下:

public class MovieRecommender {

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

    // ...
}

你也能夠指定@Qualifier註解到單獨的構造函數參數上, 以下:

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

<?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"/> <!--用main指定的值被具備相同名稱的構造函數參數自動裝配-->

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> <!--用action指定的值被具備相同名稱的構造函數參數自動裝配-->

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

做爲一種回退匹配, bean的名稱被認爲是默認的指定值. 所以,你能夠定義一個idmain的bean替代內部的指定元素, 結果是同樣的. 雖然你能夠使用bean名稱引用特定的bean, @Autowired根本上是使用可選語義的修飾語進行類型驅動注入.這就意味着限定的值,即使有名稱後備, 老是會限定在匹配類型範圍內. 他們語義上不會表達爲對一個beanid的引用. 好的限定值是main,MEMApersistent, 表達獨立於bean的id外的一種特定角色, 這個bean的id多是自動生成的匿名定義, 就像前面例子中同樣.

限定還可被用到集合, 就像前面討論的同樣, 如:Set<MovieCatalog>. 這種狀況下, 全部匹配的bean, 根據聲明的限定, 是做爲集合注入. 這暗示了限定符不須要是惟一的. 相反他們構成了過濾標準. 例如, 你能夠使用相同的限定值"action"定義多個MovieCatalogbean, 全部他們都被注入到使用@Qualifier("action")註解的集合Set<MovieCatalog>中.

在類型匹配的候選時, 讓限定值依賴bean名稱選擇, 不須要在注入點添加@Qualifier註解. 若是沒有其餘解析指令(如限定符或primary標記), 對於非單一依賴的狀況, Spring經過目標bean的名稱並選擇相同名稱的候選來匹配注入點名稱(也就是,域名稱或參數名稱).

也就是說, 若是你想經過名稱來表達註解驅動的注入, 就不須要使用@Autowired, 就算他能在類型匹配的候選中進行選擇. 相反, 使用JSR-250 的 @Resource 註解, 這個註解語義上定義了特定目標bean的惟一名稱, 聲明的類型和匹配過程無關. @Autowired有不一樣的語義: 在經過類型選擇bean後, 特定的String限定的值被認爲僅僅是侯選中的(例如, 匹配account限定將依賴於相同的限定標籤).

對於自己定義爲集合的bean, 如Map或數組類型, @Resource是好的解決方法, 經過名字引用特定的集合或數組bean. 也就是說, 自從4.3版本開始, 你也能夠使用@Autowired類型匹配算法來匹配Map和數組類型, 只要元素類型信息保存在@Bean的返回簽名類型或集合繼承中. 在這種狀況下, 如上一段所述, 你能夠使用限定值在相同類型中進行選擇.

從4.3開始, 對於注入, @Autowired也被認爲是自引用的(也就是引用返回到當前被注入的bean上). 注意,自我注入是個回退. 通常的對其餘組件的依賴每每具備優先權. 從這個意義上說, 自我引用不在通常候選範圍所以歷來不是主要的. 相反,他們通常是最後的選擇. 實際上你應該使用自我引用做爲最後的手段(例如經過bean的事務代理調用同一實例的其餘方法). 在這種場景下, 考慮重構受影響的方法到單獨的代理bean中. 想法, 你能夠使用@Resource, 這能夠經過其惟一名稱獲取到當前bean的代理.

@Autowired被用到域,構造函數,還有多個參數的方法上, 容許經過限定註解在參數級別縮小範圍. 相比之下, @Resource僅被支持在域和bean屬性的setter方法上使用. 所以, 你應該在你注入目標是個構造函數或多參數方法上使用限定.

你也能夠建立自定義的限定註解. 定義了一個註解, 並給其定義上添加了@Qualifier, 以下:

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

    String value();
}

接下來你就能夠將自定義限定添加到域或參數上, 以下:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

接下來, 你能夠爲候選bean定義提供信息了. 你能夠添加<qualifier/>標籤做爲<bean/>標籤的子元素, 而後爲其指定typevalue來匹配你自定義的註解. 類型經過註解類的全限定名匹配. 最爲替換, 若是沒有名字衝突, 你能夠使用短名稱. 下面例子展現了所有兩種方式:

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

在類路徑掃描和組件管理中, 你能夠看到XML定義的基於註解的限定元數據. 特別的, 請參看使用註解提供限定元數據.

一些狀況下, 使用沒有值的註解就足夠了. 當註解服務於更廣泛的目標而且能應用於多個不一樣類型的依賴時更爲有用. 例如, 你可能須要提供一個離線目錄供搜索, 當沒有網絡接入時. 首先, 定義簡單的註解, 以下:

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

}

而後, 添加註解到域或屬性上, 以下:

public class MovieRecommender {

    @Autowired
    @Offline //此處添加了註解@Offline
    private MovieCatalog offlineCatalog;

    // ...
}

如今bean定義僅僅須要一個限定type, 以下所示:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/>  <!--這個元素指定了限定-->
    <!-- inject any dependencies required by this bean -->
</bean>

你也能夠定義自定義限定註解, 其能夠接收更多的命名屬性或替換簡單的value屬性. 若是多個屬性值被指定到一個域或參數上, 自動包裝時, bean的定義必須匹配全部這些屬性值才能做爲候選. 參看以下所示的定義:

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

    String genre();

    Format format();
}

這個例子中Format是枚舉, 定義以下:

public enum Format {
    VHS, DVD, BLURAY
}

自動包裝的域是用自定義限定註解的, 其包含兩個屬性:genreformat, 以下所示:

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

最終, bean定義應該包含匹配的限定值. 這個示例也展現了你能夠使用bean的元數據屬性來代替<qualifier/>元素. 若是可用, <qualifier/>和他的屬性優先, 但自動封裝機制回退則使用<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>

1.9.5 使用泛型做爲自動包裝限定符

另外, 對於@Qualifier註解, 你能夠使用java泛型類型做爲限定的隱式格式. 例如, 你有以下配置:

@Configuration
public class MyConfiguration {

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

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

假設前面的bean實現了一個泛型接口(也就是Store<String>Store<Integer>), 你能夠@Autowire這個Store接口, 而且泛型被用來做爲限定, 以下:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

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

泛型限定也用來自動裝配集合列表,Map接口和數組. 下面例子自動裝配了泛型List:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

1.9.6 使用CustomAutowireConfigurer

CustomAutowireConfigurer是一個BeanFactoryPostProcessor, 容許你註冊你本身自定義的限定註解類型, 就算它們不是被Spring的@Qualifier註解的. 下例展現瞭如何使用CustomAutowireConfigurer:

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver經過下面的方式決定自動裝配候選:

  • 每一個bean定義的autowire-candidate
  • 任何<beans/>元素上可用的default-autowire-candidates模型
  • 使用了@Qualifier的註解以及經過CustomAutowireConfigurer註冊的自定義註解

當多個bean被限定爲自動裝配的候選, 首要項的決策遵循: 若是候選中存在primary屬性且值爲true, 則它被選中.

1.9.7 使用@Resource注入

Spring支持使用JSR-250規範的@Resource註解進行注入, 其(javax.annotation.Resource)可以用在域或者setter方法上. 這是JavaEE的一種廣泛模式: 例如, 在JSF-管理的bean和JAX-WS端點中. Spring爲Spring管理的bean也支持這種模式.

@Resource獲取name屬性. 默認Spring將這個值做爲bean名稱注入. 換句話說, 它遵循按名稱語義, 以下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder")  // 這裏注入了一個@Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

若是沒有明確指定名稱, 默認名稱就是從域的名字或者setter方法導出. 在域狀況下, 它使用域名稱, 在setter方法狀況下, 它獲取bean屬性名. 下面例子展現了名爲movieFinder的bean注入到setter方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

註解提供的名稱將會被接收CommonAnnotationBeanPostProcessor通知的ApplicationContext解析爲bean的名稱. 名稱能夠經過JNDI解析, 若是你顯式配置了Spring的SimpleJndiBeanFactory. 儘管能夠, 但咱們建議你依賴默認的行爲並使用Spring的JNDI查找來保證間接級別.

在沒有顯式的名稱指定的@Resource使用場景下, 相似於@Autowired,@Resource查找主類型匹配而不是命名bean, 而且也能夠解析熟知的依賴:BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, 和 MessageSource 接口.

所以, 下例中,customerPreferenceDao域首先查找一個名爲"customerPreferenceDao"的bean 而且回退到主類型匹配的類型CustomerPreferenceDao:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; // `context`域是注入了熟知的依賴類型`ApplicationContext`

    public MovieRecommender() {
    }

    // ...
}

1.9.8 使用@PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor不只識別@Resource註解, 也能夠識別JSR-250生命週期註解:javax.annotation.PostConstructjavax.annotation.PreDestroy. Spring 2.5 引用的, 對於這些註解的支持提供了一種相應的生命週期回調機制, 如以前的初始化回調和銷燬回調章節有描述的. 假設ApplicationContext註冊了CommonAnnotationBeanPostProcessor, 一個添加了這些註解的方法將會被調用, 調用點與相應的Spring生命週期接口方法或顯式聲明回調方法相同. 下面例子中, 緩存經過初始化方法預熱並經過銷燬方法清除:

public class CachingMovieLister {

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

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

關於組合使用不一樣的生命週期機制, 能夠參看組合生命週期機制.

如同@Resource, @PostConstruct@PreDestroy註解是從JDK6到8成爲標準的java類庫的一部分. 儘管這樣, 整個javax.annotation包從JDK9的核心java模塊中獨立了, 並最終在JDK11中被刪除.若是要用, 則須要從Maven中心引用javax.annotation-api架構, 簡單的將其添加到類路徑下.

1.10 類路徑掃描和託管組件

本章中大部分例子都將使用XML來指定用於Spring容器生產BeanDefinition的配置元數據. 前面章節(基於註解的容器配置)闡述如何經過代碼級別的註解來生成大量配置元數據. 就算在那些示例中, 最基礎的bean定義依然是顯式定義在XML文件中的, 而註解僅僅是驅動了DI. 本節描述一種隱式的經過掃描類路徑方法探測候選組件的方式. 候選組件是一組按必定過濾規則匹配的並被容器註冊的相關bean定義. 這就移除了使用XML來完成bean註冊的方式. 相反, 你能夠使用註解(例如:@Component), AspectJ類型表達式, 或者你自定義的過濾規則來篩選哪些類定義能註冊到容器中.

Spring 3.0 開始. 經過Spring的Java配置項目提供的一些特性成爲了Spring核心的一部分. 這就容許你使用Java而不是傳統的XML來定義bean. 能夠參看@Configuration, @Bean, @Import, 和 @DependsOn註解示例來了解如何使用這些特性.

1.10.1 @Component和更多註解

@Repository註解是爲不少履行一個倉儲角色(等同於數據訪問對象或DAO)的諸多類上的一個標記. 使用這個標記的一個用法就是自動解析異常, 如同在異常解析一節描述的.

Spring 提供了更多註解: @Component, @Service, 和 @Controller. @Component是通常的使用在Spring管理的組件上的註解. @Repository, @Service, 和 @Controller是比@Component更具針對性的用例(各自分別在持久化,服務和表現層使用). 所以, 你能夠在你的組件上使用@Component, 但使用@Repository, @Service, 和 @Controller替代將使你的類在工具處理時或聯合使用方面時更合理. 例如, 這些註解提供了理想的目標切入點. @Repository, @Service, 和 @Controller也能在未來Spring版本中提供更多的語義.所以, 若是你在你的服務層中選擇使用@Component@Service, @Service是更明確的更好的選項. 相似的, 如前所述, @Repository在持久層已經獲得異常解析方面的支持.

1.10.2 使用元註解和組合註解

許多Spring提供的註解能夠做爲元註解用到你的代碼中. 元註解是能夠應用到另外一個註解上的註解. 例如, 前面提到的@Service註解就是使用@Component元註解的, 以下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Component 將致使 @Service 與 @Component 同樣被對待
public @interface Service {

    // ....
}

你也能夠組合使用元註解用來建立組合註解. 例如, Spring MVC 的 @RestController是由@Controller@ResponseBody組合的.

更多的, 組合註解可以選擇從新定義元註解的屬性來自定義. 若是你想要僅暴露元註解中的一些屬性的話這就是有用的. 例如, Spring的@SessionScope註解硬編碼了scope的名稱爲session, 但仍然容許自定義proxyMode. 以下是SessionScope的定義:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

而後你能夠不聲明proxyMode來使用@SessionScope:

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

你也能夠重寫proxyMode的值, 以下所示:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

更多細節請看wiki頁面的Spring註解編程模型

1.10.3 自動探測類和註冊bean定義

Spring能自動探測更多的類並使用ApplicationContext註冊相關的BeanDefinition實例.例以下面的兩個類對於這樣的自動探測是合適的:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

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

爲了自動探測並註冊相關的bean, 你須要添加@ConponentScan@Configuration類上, basePackages屬性時兩個類通用的包(若是多個包的話,你能夠使用逗號或分號或空格分割的列表,將每一個類的父包包含其中).

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

爲了簡潔, 上面例子能夠使用註解的值屬性(也就是@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>元素了.

類路徑包的掃描須要相關的目錄包含在類路徑下. 當你使用Ant打包JARs是, 確保沒有爲JAR任務打開"僅文件"開關. 同時, 類路徑目錄可能會在一些環境下因爲安全策略而沒有暴露--例如, JDK1.7.0-45的獨立app或更高(須要信任類庫設置在你的清單中, 參看http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).

在JDK9的模塊路徑(Jigsaw)下, Spring類路徑掃描一般按預期執行. 儘管如此, 請確保你的組件類導出在module-info下. 若是你指望Spring調用你類中的非公有變量, 請確保他們是'opened'(也就是,在module-info描述符下,他們使用opens聲明替換exports聲明).

更多的, 當你使用component-scan元素時, AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor二者都是隱式包含的. 這意味着這兩個組件被自動探測並一塊兒包裝了--都沒有任何XML提供的bean配置元數據.

你能夠經過包含annotation-config屬性並設置爲false來關閉 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor.

1.10.4 使用過濾器來自定義掃描

默認狀況下, 用@Component, @Repository, @Service, @Controller標記的類, 或者使用@Component元註解的自定義註解標記的類是探測的候選組件. 儘管這樣, 你也能夠經過應用過濾器修改和擴展行爲. 爲註解@ComponentScan添加includeFiltersexcludeFilters參數(或者component-scan元素的include-filterexclude-filter子元素). 每一個元素都須要typeexpression屬性, 下面的表格描述了過濾選項:

表5. 過濾類型

過濾類型 示例表達式 描述
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"倉儲代替的配置:

@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, @Controller, 或 @Configuration 註解的類的自動探測失效了.

1.10.5 在組件中定義元數據

Spring組件也能爲容器貢獻bean定義元數據. 你能夠經過使用@Configuration註解的類裏面的@Bean註解來定義bean元數據. 下面例子展現了這種方式:

@Component
public class FactoryMethodComponent {

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

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

上面的類是Spring的一個組件,work()方法中它有應用特定的代碼. 它也貢獻了一個bean的定義,有個引用方法publicInstance()的工廠方法. @Bean註解標識了工廠方法和其餘bean定義的屬性, 如經過@Qualifier指定的限定值. 其餘方法級別的註解多是特定的@Scope,@Lazy以及自定義限定註解.

除了組件初始化角色, 你也能夠將@Lazy註解放在由@Autowired@Inject標記的注入點上. 在這個上下文中, 它最終注入了一個延遲解析代理.

自動裝配域和方法是受支持的, 如前所述, 同時對@Bean方法有更多的自動封裝的支持. 以下所述:

@Component
public class FactoryMethodComponent {

    private static int i;

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

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

示例自動爲類型爲String的參數country自動裝配了另外一個名爲privateInstance的bean的age屬性值. Spring表達式語言元素經過註解#{<expression>}定義了值. 對於@Value註解, 表達式解析器在解析表達式文本的時候查找bean的名稱.

從Spring4.3開始, 你可能須要聲明一個參數類型爲InjectionPoint的工廠方法(或者它的特定的子類如DependencyDescriptor), 來訪問請求的注入點來觸發當前bean的建立. 注意這種僅適用於真實的bean實例建立, 對於已存在實例的注入不適用. 做爲其結果, 這個特性對於原型bean更有意義. 對於其餘做用域, 工廠方法僅看到scope內觸發建立新bean實例的注入點(例如, 觸發建立延遲加載單例bean的依賴項).這種狀況下你能夠謹慎的使用注入點元數據. 下面例子展現瞭如何使用InjectionPoint:

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

普通Spring組件中的@Bean方法與其餘在Spring@Configuration類中的其餘同輩們處理是不一樣的. 不一樣之處在於, @Component類沒有用CGLIB加強用來切入方法和域的調用. CGLIB代理是一種調用@Configuration類中@Bean註解的方法和域來建立引用到相關對象的bean元數據的方法.此類方法不會被普通的java語義調用, 而是爲了提供非正常的生命週期管理和Spring的Bean代理而貫穿容器, 即便是經過編程方法調用@Bean方法引用其餘bean時. 相反, 調用@Component類中的@Bean方法或域時使用標準java語義, 沒有其餘特定CGLIB的處理或應用其餘限制.

你可能聲明@Beanstatic, 容許調用它們,而無需將其包含的配置類做爲實例建立。這在定義post-processor的bean時有特殊的意義(例如, 類型BeanFactoryPostProcessorBeanPostProcessor), 由於這樣的bean會被容器生命週期中更早的初始化, 而且在這個點上應該避免觸發其餘配置部分.

調用靜態的@Bean方法歷來不會被容器切入, 就算是@Configuration類(如前所述), 因爲技術限制: CGLIB 子類僅僅能覆蓋非靜態方法. 所以, 對另外一個@Bean方法的直接調用具備java語義, 結果一個獨立的實例直接由工廠方法返回.

@Bean方法的Java語言的可見性不會當即對Spring容器中的bean定義產生影響. 你能夠自由的在非@Configuration類中聲明工廠方法, 或者在任何地方聲明靜態方法. 儘管能夠, @Configuration類中普通的@Bean方法須要是可覆蓋的-- 也就是他們不能被聲明爲private或者final.

在提供的組件或配置類中@Bean方法能夠被發現, 一樣在java8的由組件和配置類實現的接口中的默認實現方法也能夠. 從Spring4.2開始, 這就容許在組合複雜配置時有更大的靈活性, 甚至經過Java8的默認辦法使多繼承成爲可能.

最終,一個類中可能持有相同bean的多個方法, 做爲多工廠方法的安排, 這些取決於於運行時可用依賴. 這與選擇最佳構造函數或配置中的工廠方法是相同的算法: 在構造時會選擇具備最大數量的依賴項的變量,即容器如何在多個@Autowired構造函數之間進行選擇。

1.10.6 命名的自動探測組件

當一個組件被做爲掃描過程的一部分探測到時, 它的bean名稱經過掃描者知道的BeanNameGenerator策略生成. 默認狀況下,任何Spring的註解(@Component, @Repository, @Service, 和 @Controller)包含一個名字value所以提供了相應bean定義的名稱.

若是註解不包含value或被其餘組件探測(例如自定義過濾器), 默認的bean名稱生成器會返回首字母小寫的不合格類名. 例如, 付過下面的組件類被探測到, 他們的名字應該是myMovieListermovieFinderImpl.

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

若是你不想依賴默認的bean命名策略, 你能夠提供自定義的bean命名策略. 首先,實現BeanNameGenerator接口, 而且保證包含了無參構造函數. 接着, 當配置掃描者的時候提供全限定類名, 以下例子中的註解和bean定義展現的:

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

做爲通用規定, 考慮使用註解指定名稱, 當其餘組件顯式引用到它時. 另外一方面,當容器負責裝配時自動生成的名稱就足夠了.

1.10.7 爲自動探測組件提供做用域

Spring託管組件同樣, 默認的和大多數通用的scope是singleton. 雖然如此, 有時候你須要經過@Scope指定一個不一樣的做用域. 你能夠爲其指定名字, 以下所示:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

@Scope註解僅在具體的bean類上(註解的組件)或工廠方法上(@Bean方法)是自省的. 相比於XMLbean定義, 沒有bean定義繼承的概念, 而且類級別的繼承與元數據不相干.

在Spring上下文中更多的關於web特定的做用域如"request","session"等,請參看Request,Session,Application和WebSocket做用域. 這些做用域有預置的註解, 你也能夠使用Spring元註解的方式組合你本身的做用域註解: 例如, 使用@Scope("prototype")自定義註解,可能須要聲明一個自定義的scope-proxy模式.

爲做用域解析提供一個自定義策略而不是依賴註解的方式, 你能夠實現接口ScopeMetadataResolver. 確保包含一個無參構造函數. 接着你在配置掃描的時候提供一個全限定名, 以下例子中的註解和bean定義所示:

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

當使用非單例做用域時, 也許必要爲做用域的對象生成代理. 緣由在做用域bean做爲依賴項中有描述. 爲了達到此目的, component-scan元素的一個scoped-proxy屬性時可用的. 三種可能的值分別是: no,interfaces,和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>

1.10.8 Qualifier元數據註解

在使用修飾符來微調基於註解的裝配中討論了@Qualifier註解. 那一節中的例子展現了在解析自動裝配的候選的時候, @Qualifier註解和自定義修飾註解提供的更好的控制. 由於這些例子基於XML bean定義, 修飾元數據是經過在做爲候選的bean元素中使用qualifiermeta子元素來實現的. 當依賴於類路徑掃描的自動探測組件時, 你能夠在候選類上添加限定元數據註解. 下面三個例子展現了這種技術:

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

對大多數基於註解的變體, 記住註解元數據是綁定到類定義自身的, 使用XML容許給相同類型的多個bean提供不一樣的限定元數據, 由於元數據是提供給每一個實例,而不是每一個類型.

1.10.9 候選組件生成索引

雖然類路徑掃描是很快的, 對於大型程序, 也能夠經過在編譯時建立一個靜態候選列表在提供啓動性能. 在這種模式下,自動掃描的全部模塊必須必須使用這種機制.

@ComponentScan<context:component-scan>指令必須保持不變, 以請求上下文掃描特定包中的候選. 當ApplicationContext探測到這麼個索引, 則自動使用它而不是掃描類路徑.

生成索引, 要給每一個包含被掃描的目標組件的模塊添加一個額外的依賴. 下例展現了Maven中的配置:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.2.0.BUILD-SNAPSHOT</version>
        <optional>true</optional>
    </dependency>
</dependencies>

對於Gradle4.5或更早版本, 依賴須要聲明在compileOnly配置中, 以下例子所示:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.2.0.BUILD-SNAPSHOT"
}

對於Gradle4.6及之後版本, 依賴須要聲明在annotationProcessor配置中, 以下例子所示:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:5.2.0.BUILD-SNAPSHOT"
}

處理過程將生成一個META-INF/spring.components文件, 包含在jar文件.

當在你的IDE中使用此模式的時候, spring-context-indexer必須做爲註解處理器註冊, 來確保當候選組件更新時索引頁同步更新.

META-INF/spring.components存在在類路徑下時, 索引自動生效. 若是索引是特定對某些庫(或用例)可用但不是爲整個程序構建時, 你能夠經過spring.index.ignore設置爲true來回退到標準的類路徑排列(好像根本不存在索引), 不管是做爲系統屬性或設置在類路徑下的spring.properties文件.

1.11 使用JSR330標準註解

Spring3.0 開始, Spring提供了對JSR-330標準註解(依賴注入)的支持. 這些註解與Spring註解以一樣的方式被掃描. 使用它們你須要在類路徑下引入相關的jar.

若是你使用Maven, javax.inject在標準的Maven倉儲下是可用的(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/). 你能夠添加下面依賴到pom.xml:

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

1.11.1 使用@Inject@Named依賴注入

代替@Autowired註解, 也能夠使用@javax.inject.Inject以下:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    public void listMovies() {
        this.movieFinder.findMovies(...);
        ...
    }
}

如同@Autowired, 你能夠將@Inject使用在域級別,方法級別和構造參數級別. 並且你可能聲明你的注入點爲一個Provider, 容許按需訪問較短做用域的bean或者經過Provider.get()調用延遲訪問其餘bean. 下例提供了上面例子中的一個變體:

import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

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

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        ...
    }
}

若是你想爲注入的依賴項使用限定名稱, 能夠使用@Named註解, 以下例:

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

    // ...
}

如同@Autowired,@Inject也能夠與java.util.Optional@Nullable一同使用. 在這裏這是更合適的, 由於@Inject沒有required屬性. 下面兩個例子展現瞭如何使用@Inject@Nullable:

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

1.11.2 @Named@ManagedBean: 標準可替換@Component的註解

替換@Component, 也能夠使用@javax.inject.Namedjavax.annotation.ManagedBean, 以下所示:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

使用沒有指定名稱的@Component是很廣泛的. @Named能夠用一樣的風格, 以下:

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@ManagedBean時, 你能夠像使用Spring註解同樣使用組件掃描, 以下所示:

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

不一樣於@Component, JSR-330 的 @Named 和 JSR-250 的 ManagedBean註解不能組合使用. 你應該使用Spring場景模型或者建立自定義註解.

1.11.3 JSR-330 標準註解的限制

使用標準註解, 你應該知道一些顯著的特性是不可用的, 以下表所示:

表6 Spring組件模型元素與JSR-330變體的區別

Spring Javax.inject.* javax.inject限制/說明
@Autowired @Inject @Inject沒有'required'屬性. 能夠用Java8的Optional替代
@Component @Named/@ManagedBean JSR-330不提供組合模型,僅僅是一種標識命名模型的方式
@Scope("singleton") @Singleton JSR-330的默認做用域相似Spring的prototype. 因此爲了保持與Spring默認的一致性, JSR-330 的bean聲明在容器中默認是singleton的. 爲了使用非singleton的其餘做用域, 你須要使用Spring的@Scope註解. javax.inject也提供了@Scope註解.不過這個僅僅用來建立你本身的註解時使用.
@Qualifier @Qualifier / @Named javax.inject.Qualifier僅是爲了建立自定義限定的元註解.具體的string類型的限定符(如同Spring的@Qualifier帶有一個值)能夠是經過javax.inject.Named關聯
@Value - 無等效的
@Required - 無等效的
@Lazy - 無等效的
ObjectFactory Provider javax.inject.Provider是Spring的ObjectFactory的替代, 但僅僅有一個短的方法get(). 它也能夠用在與Spring@Autowired或無註解構造函數以及setter方法上組合使用

1.12 基於Java的容器配置

這節內容涵蓋了如何在java代碼中使用註解配置容器. 包含下面主題:

  • 基本概念: @Bean@Configuration
  • 使用AnnotationConfigApplicationContext初始化Spring容器
  • 使用@Bean註解
  • 使用@Configuration註解
  • 組合基於java的配置
  • Bean定義檔案
  • PropertySource抽象
  • 語句中的佔位符解析

1.12.1 基本概念: @Bean@Configuration

Spring中的基於Java的配置支持和核心是@Configuration註解類和@Bean註解的方法.

@Bean註解用來代表一個方法實例化,配置,並初始化出有Spring容器管理的新對象.對於熟悉Spring的<beans/>XML配置, @Bean註解扮演了與<bean/>元素相同的角色. 你能夠與Spring@Component一塊兒使用@Bean註解方法. 即便如此, 他們老是跟@Configurationbean一塊兒使用的.

@Configuration註解的類代表它的主要目的是做爲bean定義的源. 並且@Configuration容許經過調用同一個類中的其餘@bean方法來定義bean間的依賴關係. 可能最簡單的@Configuration類以下:

@Configuration
public class AppConfig {

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

上面的AppConfig類等同於下面的<beans/>XML配置;

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

全@Configuration vs 輕@Bean模式

@Bean方法在沒有被@Configuration註解的類中聲明時, 它們會做爲輕型模式被處理. 在@Component中的bean方法或在普通舊的類中的bean方法會被認爲是輕型的, 與包含的類有不一樣的主要目的, 而且@Bean方法是種額外的好處.例如, 服務組件可能經過各合適的組件類附加的@Bean方法暴露管理視圖給容器. 此情景下, @Bean方法是通用的工廠方法機制.

不一樣於全@Configuration, 輕量@Bean方法不能聲明內部bean依賴. 相反, 他們操做包含他們組件的內部狀態, 以及可選擇的它們聲明的參數. 這樣的@Bean方法所以不能調用其餘@Bean方法. 每一個這個的方法僅僅是對特定bean的引用的工廠方法, 不須要任何運行時的特定語義. 積極的一個影響是運行時沒有CGLIB子類被應用, 所以在類設計方面沒有任何限制(也就是,包含類多是final的等等).

廣泛場景下, @Bean方法在@Configuration類內部聲明, 要保證full模式老是被使用並跨方法引用所以直接關聯到容器生命週期管理. 這就阻止了相同的@Bean方法經過普通Java調用, 從而幫助減小lite模式下難以追蹤的bug發生.

@Bean@Configuration註解在接下來的章節中會更深的討論. 首先, 咱們掃描了經過使用Java配置的方式建立容器的方式.

1.12.2 使用AnnotationConfigApplicationContext初始化Spring容器

接下來的章節記錄了Spring的AnnotationConfigApplicationContext, Spring3.0引入的. 多功能的ApplicationContext實現能接收不只僅是Configuration類做爲輸入, 還有@Component類以及JSR-330元數據註解的類.

@Configuration類做爲輸入時, @Configuration類自己被做爲bean定義註冊並將其內部全部的@Bean方法也被註冊爲bean定義.

@Component和JSR-330類做爲輸入時, 他們被註冊爲bean定義, 而且假定如@Autowired@Inject的DI元數據在這些類的內部按需使用.

簡單的結構

與Spring的XML文件用來被初始化ClassPathXmlApplicationContext時的輸入同樣, 你能夠使用@Configuration類做爲初始化AnnotationConfigApplicationContext時的輸入. 這容許徹底不適用XML的方式, 以下所示:

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,Dependency1Dependency2使用了諸如@Autowired的Spring DI註解.

經過register(Class<?> ... )編程構建容器

你能夠經過無參構造實例化AnnotationConfigApplicationContext,而後經過register()方法配置它. 這種方法在編程構建AnnotationConfigApplicationContext是特別有用. 以下所示:

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();
}
使用scan(String…​)開啓組件掃描

開啓組件掃描, 你能夠在@Configuration類上註解以下:

@Configuration
@ComponentScan(basePackages = "com.acme")  // 這個註解啓動了組件掃描
public class AppConfig  {
    ...
}

有經驗的Spring用戶可能更熟悉XML聲明, 它是從Spring的context:命名空間, 以下例子所示:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在前面例子中, com.acme包被掃描以查找任何@Component註解的類, 還有那些註冊在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元註解的, 因此他們是掃描的候選. 在前面的例子中, 假設AppConfig聲明在com.acme包內部(包括更深層次的包內), 在調用scan()期間它被選中. 依賴refresh(), 它全部的@Bean方法被處理並註冊在容器內部.

使用AnnotationConfigWebApplicationContext支持WEB程序

一種WebApplicationContextAnnotationConfigApplicationContext變體是AnnotationConfigWebApplicationContext. 當配置ContextLoaderListenerSevlet監聽器, Spring MVC DispatcherServlet等時能夠使用這個實現. 下面的web.xml片斷配置一個典型的Spring MVC Web程序(注意contextClass context-param 和 init-param 的使用).

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- 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>
</web-app>

1.12.3 使用@Bean註解

@Bean是方法級別的註解, 而且與XML 的 <bean/>元素同源. 這個註解支持一些<bean/>屬性,如* init-method * destroy-method * autowiring * name.

@Configuration註解的或者在@Component註解的類中均可以使用@Bean註解.

聲明一個Bean

要聲明bean, 你能夠在一個方法上添加@Bean註解. 在ApplicationContext內部, 這個方法將被它的返回值類型註冊到容器的bean定義. 默認bean的名字和方法名稱是同樣的. 下面示例展現了@Bean的聲明:

@Configuration
public class AppConfig {

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

上面的配置是等同於下面XML的配置:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

兩種方式都使得名爲transferService的bean在ApplicationContext可用, 綁定到一個類型爲TransferServiceImpl的實例, 以下所示:

transferService -> com.acme.TransferServiceImpl

你也能夠使用接口(或基類)聲明@Bean方法返回類型, 以下所示;

@Configuration
public class AppConfig {

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

然則, 這限制了預測類型的可見性爲接口類型(TransferService). 而後, 容器僅知道一次完整類型(TransferServiceImpl),受影響的bean單例被建立了. 非延遲加載的單例bean實例化根據他們聲明的順序初始化, 因此,依賴於當其餘組件嘗試匹配一個沒有聲明的類型時, 你可能看到不一樣類型的匹配結果(如@Autowired TransferServiceImpl, 僅在transferService被實例化時解析).

若是你持續經過聲明的服務接口引用, 你的@Bean返回類型可能安全地參與到設計決策中. 然而, 對於實現了多個接口或對於潛在引用它們實現類型的組件, 更安全的是儘量的指定返回類(至少指定引用到bean的注入點時須要的類型)

Bean依賴

@Bean註解方法可能還有隨意數量的描述建立bean所需依賴的參數. 例如, 若是咱們的TransferService須要一個AccountRepository, 咱們能夠具象出使用帶有一個參數的方法, 以下:

@Configuration
public class AppConfig {

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

這種解析機制比較其構造函數依賴而言至關, 更多細節參看相關章節.

獲取生命週期回調

任何用@Bean註解的類定義都支持通常的生命週期回調, 而且能夠使用JSR-250@PostConstruct@PreDestroy註解. 參看JSR-250註解獲取更多細節.

通常的Spring的生命週期回調也徹底支持. 若是bean實現了InitializingBean,DisposableBean,或者Lifecycle, 他們各自的方法就會被容器調用.

標準的*Aware接口(如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)也是徹底支持的.

@Bean註解支持指定任意的初始化和銷燬回調方法, 如同XML中在<bean/>元素上的init-methoddestroy-method屬性, 以下:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

默認Java配置定義的bean有個公有的closeshutdown方法自動參與銷燬回調. 若是你定義了相關的方法又不想在容器關閉時調用,你能夠添加@Bean(destroyMethod="")到bean的定義來關閉默認的(inferred)推斷模式.

你可能想在獲取JNDI資源時默認這麼作, 這種資源的生命週期是在程序外管理的. 特別的, 確保在操做DataSource時老是這麼作, 由於在Java EE 程序服務器上已知是有問題的.

下面示例展現瞭如何阻斷自動的銷燬回調:

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

此外, 使用@Bean方法, 一般使用編程方式使用JNDI查找, 也使用Spring的JndiTemplateJndiLocatorDelegate幫助類或者直接的JNDIInitialContext, 但不是JndiObjectFactoryBean變量(這將迫使你聲明返回類型爲FactoryBean而不是真實的目標類型, 增長其餘@Bean方法中跨引用調用的難度, 這些方法原本要引用提供的資源).

在上面的BeanOne示例代碼中, 構造時調用init()方法是等效的, 以下所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

直接與java操做時, 你能夠作任何你想要作的事情, 而不需依賴容器生命週期.

指定Bean做用域

Spring包含@Scope註解因此你能夠指定bean的做用域.

使用@Scope註解

你能夠爲@Bean註解的bean定義指定做用域. 能夠使用在bean做用域中指定的標準做用域.

默認做用域是singleton, 但能夠覆蓋, 以下:

@Configuration
public class MyConfiguration {

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

經過做用域代理, Spring提供了一種方便的方法與做用域依賴協做. 最簡單的方式是在XML配置<aop:scoped-proxy/>添加建立的代理.在Java中配置bean使用@Scope註解的proxyMode屬性提供了等效的支持. 默認沒有代理(ScopedProxyMode.NO), 但能夠指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES.

若是將XML中的引用文檔(參看做用域代理)移植到Java的@Bean, 它看起來以下:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
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屬性覆蓋, 以下:

@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}
Bean別名

如在命名Bean中討論的, 有時候迫切須要給bean指定多個名稱, 也就是別名. @Beanname屬性能夠接收一個String類型的數組來達到這個目的. 下面例子展現了一個bean中的多個別名:

@Configuration
public class AppConfig {

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

有時候爲bean提供更多細節的描述是有用的. 特別是在bean暴露給監控的時候(也許經過JMX).

@Bean添加描述, 你能夠使用@Description註解, 以下:

@Configuration
public class AppConfig {

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

1.12.4 使用@Configuration註解

@Configuration是一個代表對象是bean定義源的類級註解. @Configuration經過公有的@Bean註解方法聲明類. 調用@Configuration類內的@Bean方法也能夠用來定義內部bean依賴. 參看基本概念:@Bean@Configuration的基本介紹.

注入內部bean依賴

當bean有對其餘bean的依賴時, 表達這種依賴如同一個調用另外一個bean方法這樣簡單, 以下所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

前面的例子中, beanOne經過構造函數接收一個beanTwo.

這種內部bean依賴聲明的方法僅適用於在@Configuration內部定義的@Bean. 適用尋常的@Component類不能聲明內部bean依賴的狀況.

查找方法注入

如前面提到的, 查找方法注入是應該少用的高級特性. 在一個單例bean擁有原型bean依賴的場景它是有用的. 使用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的子類, 其createCommand()方法被覆蓋, 這個方法用來查找一個原型指令對象. 以下所示:

@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 createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
關於基於Java配置內部工做的更多信息

下面的兩個例子, 此例中一個@Bean註解方法被調用兩次.

@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並返回, 你可能預測會有兩個實例(每一個服務一個). 這種概念是有問題的: Spring中, bean默認是singleton做用域. 這就是魔法時刻: 全部@Configuration類都在啓動時由CGLIB生成子類了. 子類中, 在它調用父類方法並建立實例前, 子方法檢查任何緩存(做用域內)的bean.

根據你bean做用域不一樣, 這種行爲可能不一樣. 咱們這裏只講singleton.

從Spring3.2開始, 不須要添加CGLIB到類路徑了, 由於CGLIB類已經被打包到org.springframework.cglib並直接包含在Spring核心JAR中了.


因爲在啓動時CGLIB動態添加特性, 有一些限制. 特別的, 配置Configuration類必須不是final的. 雖然, 從4.3開始, 配置類中任何構造函數都是被容許的, 包含使用@Autowired或單個的默認構造函數.

若是你想避免CGLIB的限制, 考慮將你的@Bean方法聲明在非@Configuration類中(例如使用@Component類代替).@Bean之間的跨方法調用就再也不被攔截了, 因此你必須在構造函數和方法級別徹底依賴DI.

1.12.5 組合基於Java的配置

Spring基於Java的配置特性使得能夠組合註釋, 這能減小配置的複雜性.

使用@Import註解

很像XML配置中<import/>元素被用來輔助模塊化配置, @Import註解容許從其餘配置類加載@Bean定義, 以下:

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

如今, 在初始化上下文時, 不須要指定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類.

從Spring4.2開始, @Import也支持普通的組件類, 相似於AnnotationConfigApplicationContext.register方法. 若是想避免組件掃描這就是有用的, 經過使用少許配置類做爲入口顯式定義你的全部組件.

在導入的@Bean定義中注入依賴

前面的例子能夠運行可是過度簡化. 在大多數實際場景中, bean依賴會跨Configuration類. 當使用XML時, 這不是問題, 由於沒有編譯介入, 你能夠聲明ref="someBean"並信任Spring會在初始化時處理好它. 當使用@Configuration類時, java編譯器在配置模式下設置了約束, 引用其餘bean必須符合java語義.

幸運的是, 解決這個問題很簡單. 咱們已經討論的, @Bean方法能夠有任意的描述依賴的參數. 考慮下面的真實場景, 它有幾個@Configuration類, 每一個依賴描述在其餘配置中:

@Configuration
public class ServiceConfig {

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

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        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");
}

有其餘方法能夠獲得相同的結果. 記住@Configuration類僅僅是容器中的另外一個bean: 這意味着他們能夠獲得跟其餘bean同樣的好處, 能夠使用如@Autowired@Value注入等特性.

確保以這種方式注入的依賴關係只屬於最簡單的類型。@Configuration類在上下文初始化過程當中處理得很是早,經過這種方式強制注入依賴項可能會致使意外的早期初始化。儘量使用基於參數的注入,如前面的示例所示。

同時請特別當心的使用BeanPostProcessorBeanFactoryPostProcessor定義. 這些應該老是聲明爲static @Bean方法, 不觸發容器配置類的初始化. 不然, @Autowired@Value不會在配置類上工做, 由於它過早的做爲bean被建立了.

下面展現一個bean如何被另外一個bean自動裝配:

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

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

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.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");
}

@Configuration類上進行構造參數注入是從Spring4.3開始支持的. 注意若是僅有一個構造參數就不須要指定@Autowired在目標bean定義上.在前面例子中, @Autowired無需在RepositoryConfig上指定.

全限定導入bean,便於導航

前面的場景中, 使用@Autowired工做的很好而且提供了急需的模塊化, 但明確的決定包裝bean在哪裏依然是模糊的. 例如, 開發人員查找ServiceConfig, 你怎麼指定@Autowired AccountRepository定義在哪裏? 代碼裏面沒有明確, 而且這還好. 記住Spring Tool Suite 提供了工具化能呈現包裝的圖標, 這多是你須要的所有. 你的Java IDE也能簡單的找到全部的聲明和AccountRepository類的使用以及很快展現@Bean方法和返回值.

萬一這種模糊性不可接受而且你想從IDE直接從@Configuration類導航到另外一個, 就要考慮包裝配置類自己了, 以下:

@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是徹底顯式定義的. 雖然, ServiceConfig如今被緊密耦合到RepositoryConfig了. 這是折中. 經過使用基於接口或抽象類的@Configuration類使用, 緊耦合能或多或少獲得緩解. 以下:

@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類和他們的依賴於一般的導航基於接口的代碼沒什麼差異.

若是你想對特定bean的啓動建立產生影響, 考慮聲明他們爲@Lazy的(對於首次訪問而不是啓動)或者@DependsOn的(確保其餘bean在當前bean前建立, 超出後者直接依賴的含義).

條件包含@Configuration類或@Bean方法

根據隨機的系統狀態, 條件化的啓動和禁止整個@Configuration類或單獨的@Bean方法是有用的. 一個廣泛的例子就是,當profile在Spring的Environment來使用@Profile註解來激活bean(參看Bean定義Profiles獲取更多).

@Profile註解其實是使用更靈活的@Conditional註解來實現的. @Conditional註解代表特定的org.springframework.context.annotation.Condition實現應該在@Bean被註冊前諮詢到.

接口Condition的實現提供了一個matches(...)方法, 返回true,false. 例如, 下面代碼展現了@Profile使用到的Condition實現:

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

查看@Conditionaljava文檔獲取更多細節.

組合Java和XML配置

Spring的@Configuration支持並非爲了100%達到替換XML配置的目的. 一些設施, 例如Spring的XML命名空間, 保留了配置容器更理想的方式. 萬一XML配置更方便或更須要, 你有一個選擇: 要麼容器是XML爲中心的方式,例如ClassPathXmlApplicationContext, 或者是一個使用AnnotationConfigApplicationContext@ImportResource註解導入XML的以Java配置爲主的方式.

使用@Configuration類的XML爲主方式

可能用XML去啓動Spring容器並用ad-hoc鉤子方式包含@Configuration類更好. 例如, 在大型的現有的代碼中使用XML, 按需建立@Configuration類並從已有的XML文件中包含進來更爲容易. 本節的後面, 咱們會講述在XML爲主的程序使用@Configuration的這種選擇.

聲明@Configuration類爲普通的<bean/>元素

記住@Configuration類是容器中的最終的bean定義. 在這個系列的例子中, 咱們建立了名爲AppConfig@Configuration類並做爲<bean/>定義包含在system-test-config.xml中. 由於<context:annotation-config/>是開着的, 容器識別出@Configuration註解併合理處理了定義在AppConfig中的@Bean方法.

下面例子展現了Java中的通常配置:

@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引用它, 而且也不可能顯式經過名字被容器獲取. 類似的, DataSourcebean僅僅是按類型裝配, 全部顯式的id不是嚴格須要的.

使用 來拾取@Configuration

由於@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>
使用@ImportResource導入XML的@Configuration類爲主的方式

程序中用了@Configuration類做爲主要的配置容器的機制, 但仍然可能須要使用少許XML. 這種場景下, 你能夠使用@ImportResource並僅定義你須要的XML. 這樣就能夠用Java爲中心的方式配置容器並將XML保持到最小. 下面例子(包含一個配置類,一個定義了bean的xml文件, 一個properties文件,以及主類main)展現瞭如何使用@ImportResource註解完成Java配置並按需使用XML:

@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);
    }
}
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);
    // ...
}

1.13 環境抽象

Environment接口是集成到容器的一個抽象, 抽象出兩個關鍵的程序環境方面: profiles 和 properties.

profile是一個命名的, bean定義的邏輯組,這些bean只有給定的profile激活才註冊到容器中的. 無論是在XML仍是經過註解定義, bean可能會被分配給一個profile. 關聯到profile的Environment對象角色是決定哪些profile當前是激活的, 還有哪些profile應該是默認激活的.

在大多數程序中, properties扮演了一個重要的角色, 而且源於一些列初始來源: properties文件, JVM系統屬性,系統環境變量, JNDI,servlet上下文參數,ad-hoc屬性對象, Map對象等. 關聯到properties的Environment對象提供給用戶一種便捷的服務接口, 來配置屬性源和解析他們.

1.13.1 Bean定義Profile

Bean定義profile提供了一種核心容器的機制, 容許不一樣環境中不一樣bean的註冊. "environment"這個單詞對不一樣的用戶有不一樣的意義, 而且在不少用例下有用, 包含:

  • 開發時在內存數據庫進行, QA或生產則查找JNDI中相同的數據庫

  • 在性能環境部署程序時, 註冊監控組件

  • 對於客戶A和客戶B部署不一樣的自定義實現.

考慮第一種用例, 在特定程序中須要須要一個DataSource. 在測試環境, 配置可能以下組織:

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

如今考慮在QA或生產環境部署程序, 假設程序的生產數據源註冊在服務器的JNDI目錄. 咱們的dataSourcebean如今以下:

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

問題是如何根據當前環境轉換這兩個變量. 隨着時間的過去, Spring的用戶想出不少方法來實現, 一般依賴於系統環境變量和XML包含了${placeholder}語句的<import/>來解決, 根據環境變量的值不一樣解析出正確的文件路徑. Bean定義profile提供瞭解決此類問題的一種核心特性.

若是咱們總結上面的用例, 咱們以須要在不一樣上下文中註冊不一樣的bean定義結束. 你可能會說你想在情況A時註冊一個profile,而情況B時註冊另外一個profile. 咱們修改你的配置以達到這一需求.

使用@Profile

@Profile註解能代表一個組件對於一種或多種激活的profile是有效的. 使用前面的例子, 咱們重寫dataSource配置以下:

@Configuration
@Profile("development")
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();
    }
}
@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");
    }
}

如早前提到的, @Bean方法,你一般經過使用Spring的JndiTemplate/JndiLocatorDelegate或直接是JNDI的InitialContext來編程實現JNDI查找, 但不使用JndiObjectFactoryBean, 由於這個變量會強制你聲明返回值爲FactoryBean類型.

profile的字符串可能包含一個簡單的profile名稱(如production)或者profile表達式. profile表達式容許更復雜的邏輯(如product & us-east). 下面操做符支持profile表達式:

  • !: 表示非
  • &: 表示並
  • |: 表示或

不適用圓括號的話你不能混用&|. 例如production & us-east | eu-central不是有效的表達式. 必須使用production & (us-east | eu-central).

你能夠使用@Profile做爲元註解來建立組合註解. 下面例子定義了一個@Production註解, 能夠用來替代@Profile("production"):

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

若是一個@Configuration類標記爲@Profile, 那全部關聯到這個類的@Bean方法和@Import都會被繞開, 除非一個或多個特定的profile被激活. 若是@Component@Configuration類被@Profile({"p1", "p2"})標記, 那這些類將不會被處理,除非'p1'或'p2'被激活. 若是profile被非操做符!做爲前綴, 那註解的元素會在profile非激活狀態下注冊. 例如, 給定@Profile({"p1", "!p2"}),註冊將在profile'p1'激活或者profile'p2'非激活狀態下發生.

@Profile也能在方法級別聲明用來包含配置類中一個特定的bean(例如, 特定bean的替換變量), 以下:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") // `standaloneDataSource`方法僅在`development`激活狀態可用
    public DataSource standaloneDataSource() {
        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("dataSource")
    @Profile("production") // `jndiDataSource`方法僅在`production`激活狀態可用
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

使用@Bean方法上的@Profile, 一種特殊場景可能會應用: 對於具備相同方法名稱重載的@Bean方法(相似構造函數重載), @Profile條件須要在全部重載方法上聲明. 若是條件不一致, 則僅在重載方法中第一個聲明處起做用. 所以@Profile不能被用在選擇具備參數簽名的方法. 同一bean的全部工廠方法之間的解析在建立時遵循Spring的構造函數解析算法。

若是你想用不一樣的profile條件定義不一樣的bean, 請使用不一樣的java方法名稱, 這些名稱經過使用@Bean的name屬性指向同一個bean名稱, 如前面的例子所示. 若是參數簽名都同樣(例如, 全部的變量都有無參構造方法), 這是在有效的java類中表示這種安排的惟一方法(由於只能有一個特定名稱和參數簽名的方法).

XML bean定義 profile

XML相對應的是<beans>元素的profile屬性. 咱們上面的例子能夠被重寫爲XML文件, 以下:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

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

也可能爲了不分開定義在不一樣的<beans/>元素, 因此定義在同一個文件中, 以下:

<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="development">
        <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>

spring-bean.xsd被限制僅僅做爲文件的最後元素出現. 這可能有助於XML文件中避免混亂.

對應的XML配置不支持前面提到的profile表達式. 雖然, 能夠經過使用!操做符. 也可能經過嵌套的profile用"and"來完成邏輯與, 以下:

<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="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>

上面例子中, dataSourcebean在 productionus-east都被激活狀況下暴露出來.

激活profile

如今咱們升級了配置, 咱們依然須要通知Spring哪一個profile被激活. 若是咱們啓動樣例程序, 咱們會看到一個NoSuchBeanDefinitionException異常拋出, 由於容器找不到名叫dataSource的bean.

能夠有幾種方式激活profile, 但最直接的是經過使用ApplicationContextEnvironment API . 以下展現瞭如何這麼作:

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

更多的, 你也能夠經過屬性spring.profiles.active激活profile, 這是經過系統變量, JVM系統屬性, web.xml中定義的servlet參數, 甚至是JNDI中的配置(查看PropertySource抽象).在集成測試中, 激活profile能夠經過使用在spring-test模塊上的@ActiveProfiles註解激活(參看使用環境profile配置上下文).

注意profile不是一個"是...或者..."的命題. 你能夠一次啓動多個profile. 編程方式來講, 你能夠提供多個profile名稱給setActiveProfiles()方法, 接收一個String...變量, 以下激活了多個profile:

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

spring.profiles.active也能夠接受一個逗號分隔的profile名稱, 以下:

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

默認profile是默認狀況下表現出的profile. 考慮下面的例子:

@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就不會被啓用.

你能夠經過使用Environment上的setDefaultProfiles()修改默認profile的名稱. 或者,經過使用屬性spring.profiles.default聲明.

PropertySource抽象

Environment抽象提供了從property源經過配置架構查找操做的能力. 以下:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

上面的代碼片斷中, 咱們看到從高層次的方式來查找Spring的my-property是否在當前環境中定義. 爲回答這個問題, Environment對象從一系列PropertySource對象中查找. PropertySource是一個對key-value對的簡單的抽象. Spring的StandardEnvironment使用兩個PropertySource 對象進行配置--一個是JVM系統屬性(System.getProperties())還有一個是系統環境變量(System.getenv()).

這些默認的屬性源是爲StandardEnvironment存在, 在獨立程序中, StandardServletEnvironment是由更多的包含servlet配置和servlet上下文參數的屬性源生成. 它可選地啓動JndiPropertySource.參看java文檔.

具體的, 當你使用StandardEnvironment時, env.containsProperty("my-property")將返回true, 若是運行時有my-property系統屬性或my-property環境變量存在的話.

查找是按層次的. 默認系統屬性優先於環境變量. 所以, 若是my-property碰巧同時出現, 在調用env.getProperty("my-property"), 系統屬性勝出並返回. 注意屬性值不是合併, 而是徹底被優先的值覆蓋.

對於普通StandardServletEnvironment, 所有層級以下, 最上面事最高優先級的:

  1. ServletConfig 參數 (若是恰當, 例如, 對於DispatcherServlet上下文)
  2. ServletContext 參數(web.xml 上下文參數)
  3. JNDI環境變量(java:comp/env/入口)
  4. JVM 系統屬性(-D命令行參數)
  5. JVM 系統環境變量(操做系統環境變量)

最重要的, 整個機制是可配置的. 也許你有自定義的propertis源想要集成到這裏. 這麼作的話, 繼承和實例化你本身的PropertySource併爲當前Environment添加PropertySources. 以下:

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

前面代碼中, MyPropertySource被以最大優先級添加. 若是它包含my-property屬性, 這個屬性將被找到並返回, 優先於任何其餘PropertySource中的my-property. MutablePropertySourcesAPI暴露了不少容許精確操做property源的方法.

1.13.3 使用@PropertySource

@PropertySource註解提供了方便的添加PropertySource到Spring的Environment的機制.

給定的app.properties文件包含了鍵值對testbean.name=myTestBean, 緊接着的@Configuration類使用@PropertySource調用testBean.getName()返回myTestBean:

@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源路徑下的${…​}都將解析爲一組已經註冊到環境中的屬性源, 以下:

@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在其餘的屬性源中定義並已經註冊(例如, 系統屬性或環境變量), 那這個佔位符將解析爲相關的值. 若是沒有, 那麼default/path將會被使用, 若是沒有默認屬性指定和解析, 那IllegalArgumentException將拋出.

@PropertySource註解是可重複的, 根據Java8的約定. 雖然能夠, 但全部@PropertySource註解須要在相同層級聲明, 直接在配置類或者做爲元註解自定義註解. 混用元註解和指令註解不建議, 由於指令註解實際上覆蓋了原註解.

1.13.4 語句中的佔位符解析

歷史上, 元素中的佔位符只能依據JVM系統屬性或環境變量解析. 這已經不是問題了. 由於Environment抽象經過容器集成, 經過它解析佔位符是簡單的途徑. 這意味着你能夠用你喜歡的方式配置解析方式. 你也能夠修改查找系統屬性和環境變量的優先級, 或者移除他們. 適當的話你還能夠添加你本身的源.

具體的, 下面的語句無論哪裏定義了customer, 只要他在Environment中有效就能工做:

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

1.14 註冊LoadTimeWeaver

LoadTimeWeaver被Spring用來在加載到JVM時動態轉化類.

啓用加載時織入, 你能夠添加@EnableLoadTimeWeaving到一個@Configuration類上. 以下:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

對應的XML配置,能夠使用context:load-time-weaver元素.

<beans>
    <context:load-time-weaver/>
</beans>

一旦爲ApplicationContext配置了任何在ApplicationContext內部均可以實現LoadTimeWeaverAware的bean,從而在收到在加載時織入的實例. 組合使用Spring的JPA支持時這一點頗有用, 對於JPA類轉化時在加載時織入的地方多是必要的. 訪問LocalContainerEntityManagerFactoryBean的文檔有更詳細介紹. 對AspectJ 加載時織入, 參看使用AspectJ加載時織入.

1.15 ApplicationContext的更多功能

在本章的描述中, org.springframework.beans.factory包提供了管理和操做bean的基本功能, 包括以編程的方式. org.springframework.context包添加了接口ApplicationContext, 這個接口擴展了BeanFactory, 另外擴展了其餘接口的功能,用來在一個更應用程序向的風格中提供附加功能. 許多人以一種徹底聲明的方式使用ApplicationContext, 並非編程的建立它, 而是依賴支持類如ContextLoader來自動實例化ApplicationContext,將其做爲Java EE Web程序啓動的一個過程.

在一個傾向框架的風格中增強BeanFactory功能, context包也提供了以下功能:

  • 使用接口MessageSource以i18n風格訪問消息
  • 經過ResourceLoader接口訪問資源,如URLs和文件
  • 經過使用ApplicationEventPublisher接口實現事件發佈, 實現了ApplicationListener接口的命名bean.
  • 多(層次)上下文加載, 經過HierarchicalBeanFactory接口, 使得每一個聚焦於特定的層, 如web層
1.15.1 使用MessageSource國際化

ApplicationContext接口擴展了叫MessageSource的接口,於是提供了國際化("i18n")功能. Spring也提供了HierarchicalMessageSource接口, 這個接口能層次性解析消息. 總之這些接口提供的功能依賴於Spring有效處理消息解析的能力. 這些定義在接口的方法包含:

  • String getMessage(String code, Object[] args, String default, Locale loc): 用來從MessageSource獲取消息的基本方法. 當指定區域沒有消息時, 默認的消息將被採用. 使用標準類庫MessageFormat提供的功能, 任何傳入的參數將替換值.

  • String getMessage(String code, Object[] args, Locale loc): 本質上跟上面方法同樣但有個區別: 沒有默認消息指定. 若是沒有消息發現, NoSuchMessageException將被拋出.

  • String getMessage(MessageSourceResolvable resolvable, Locale locale): 上面方法中使用的屬性被包裝到一個叫MessageSourceResolvable的類裏面, 這個你可用在本方法中.

ApplicationContext被加載, 它就自動加載上下文中定義的MessageSourcebean. 這個類必須有名字messageSource. 若是找到這個bean, 全部前面的方法的調用將委託給消息源. 若是沒有消息源被找到, ApplicationContext嘗試找到包含相同名稱的父容器. 若是找到了, 這個bean就做爲MessageSource使用. 若是ApplicationContext不能找到任何消息源, 則空的DelegatingMessageSource被初始化用來接受上面定義的方法的調用.

Spring提供了兩個MessageSource實現, ResourceBundleMessageSourceStaticMessageSource. 兩者都實現了HierarchicalMessageSource以便能處理嵌套消息. StaticMessageSource不多使用但提供了編程的方式添加消息到源的方法. 下例展現了ResourceBundleMessageSource:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

這個示例假設你有三個源叫format,exceptionwindows在你的類路徑下. 任何解析消息的請求都是以經過對象ResourceBundle解析消息的標準JDK方式處理的. 本例的目的,假設上面的兩個消息文件以下:

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

下面例子展現了編程執行MessageSource功能. 記住全部的ApplicationContext實現也是MessageSource實現, 因此能夠被轉化爲MessageSource接口.

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

上面的程序運行結果以下:

Alligators rock!

總之, MessageSource定義在一個名叫beans.xml的文件中, 這個文件在你類路徑的根目錄下. messageSourcebean定義經過basenames屬性引用到一系列資源bundles. 這三個文件分別叫format.properties, exceptions.properties, 還有windows.properties存在於你的類路徑下, 經過basenames屬性傳遞.

下面例子展現了傳遞給消息查找的參數. 這些參數轉化爲String對象並插入查找的佔位符中.

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }
}

execute()方法調用的結果以下:

The userDao argument is required.

提及國際化("i18n"), Spring的變量MessageSource實現遵循與標準JDK的ResourceBundle相同的區域解析和回退規則. 總之, 繼續前面定義的messageSource示例,若是您想解析針對英語消息(en-GB), 那麼你須要分別建立format_en_GB.properties, exceptions_en_GB.properties, 和 windows_en_GB.properties.

通常的, 區域解析是受程序的周圍環境管理. 接下來的例子中, 依賴於(英國)的消息將被手工的解析:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

運行結果以下:

Ebagum lad, the 'userDao' argument is required, I say, required.

你也能夠使用MessageSourceAware接口獲取任何定義的MessageSource的引用. 任何在ApplicationContext中定義的MessageSourceAware接口實現將在程序上下文的bean建立和配置時使用MessageSource注入.

做爲ResourceBundleMessageSource的替換, Spring提供了ReloadableResourceBundleMessageSource類. 這個變量支持文件格式化但比起基於標準JDK的ResourceBundleMessageSource實現更靈活. 特別的, 它容許從Spring資源路徑讀取文件(不只是類路徑)而且支持熱加載property文件(將其有效緩存). 查看java文檔ReloadableResourceBundleMessageSource獲取更多信息.

1.15.2 標準和自定義事件

經過ApplicationEvent類和ApplicationListener接口提供了ApplicationContext的事件處理. 若是bean實現了ApplicationListener接口並部署在上下文中, 每當ApplicationEvent發佈到ApplicationContext中時, 這個bean就獲得通知了. 本質上, 這是標準的觀察者模式.

從Spring4.2開始, 事件相關基礎設施被明顯加強了, 而且提供了基於註解的模型,也能夠發佈任意事件.(也就是說對象不必擴展自ApplicationEvent). 當這樣的對象發佈後, 咱們爲你包裝在一個事件中.

下面表格描述了Spring提供的標準事件:

表7 內置事件

Event Explanation
ContextRefreshedEvent ApplicationContext被初始化或者刷新後發佈(如經過使用ConfigurableApplicationContext接口的refresh()方法調用).這裏,"initialized"意味着全部的bean被加載,post-processor被探測並激活, 單例被預初始化, ApplicationContext對象準備就緒可用. 只要上下文沒有被關閉, 刷新會被屢次觸發, 所選的ApplicationContext事實上支持熱刷新. 例如XmlWebApplicationContext支持熱刷新,但GenericApplicationContext不支持
ContextStartedEvent ApplicationContext啓動後, 使用接口ConfigurableApplicationContext的方法start()將發佈該事件.這裏, "啓動"意味着全部的Lifecyclebean接收到顯式的啓動信號. 通常而言, 這個信號在顯式中止後用來重啓bean, 但它也用來啓動那些沒有被配置爲自動啓動的bean(例如, 沒有被初始化啓動的組件).
ContextStoppedEvent ConfigurableApplicationContext接口的方法stop()調用後ApplicationContext被中止是發佈該事件. 這裏"中止"意味着全部Lifecyclebean接收一個顯式的中止信號. 結束的上下文可能經過start()調用重啓
ContextClosedEvent ConfigurableApplicationContext接口的方法close()調用後,當ApplicationContext關閉時發佈. 這裏, 關閉意味着全部單例bean被銷燬. 關閉的上下文生命結束. 它不可能被刷新或重啓
RequestHandledEvent 一個WEB特定的事件,告訴全部HTTP請求被處理的bean. 這個事件在請求完成後被髮布. 這個事件僅僅對於使用了DispatcherServlet的web程序有用.

你也能夠建立和發佈你自定義的事件. 下面的例子展現了擴展ApplicationEvent基類的一個簡單類:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

發佈自定義的ApplicationEvent, 能夠調用ApplicationEventPublisher上的publishEvent()方法. 通常這是經過建立一個實現ApplicationEventPublisherAware的類並將其註冊爲一個Spring bean. 以下所示:

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 content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

在配置時, Spring探測到實現了ApplicationEventPublisherAware接口的EmailService, 並自動調用setApplicationEventPublisher(). 實際上,傳入的參數是Spring容器自身. 你經過ApplicationEventPublisher接口與之交互.

爲了接收自定義的ApplicationEvent, 你能夠建立實現了ApplicationListener接口的類並註冊爲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()方法能保持類型安全, 防止任何類型轉換. 只要願意你能夠註冊任意多的監聽器, 但要注意,默認狀況下事件監聽器是同步接收事件的. 這意味着publishEvent()方法會阻塞直到全部監聽器處理完事件. 一個同步和單線程的好處是,當監聽器獲取到一個事件, 它就會在發佈者的事務上下文可用時進行操做. 若是其餘事件發佈策略時必要的, 可參看Spring接口ApplicationEventMulticaster接口文檔.

下面例子展現了用來註冊和配置上面所述每一個類的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>

整體來看, 當emailServicesendEmail()方法被調用後, 若是有任何在黑名單郵件消息, 自定義的事件BlackListEvent就發佈了. blackListNotifierbean做爲ApplicationListener被註冊, 並接收BlackListEvent, 這裏能夠通知到恰當的某些部分.

Spring的事件機制是爲了在相同上下文內部bean之間的簡單通訊. 儘管如此, 對於大多數複雜的企業集成需求, 獨立維護的Spring集成項目提供了完整的對構建輕量,面向模式的, 事件驅動的架構的支持, 能夠構建在熟知的Spring編程模型之上.

基於註解的事件監聽器

從Spring4.2開始, 你能夠在任意的經過EventListener註解的bean方法上註冊事件監聽器. BlackListNotifier能夠重寫以下:

public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

這個方法簽名再次聲明瞭它監聽的事件類型, 但此次, 沒有實現特定的接口並有靈活的名稱. 這個事件類型也能夠經過泛型縮小範圍. 只要在繼承架構內真實類型能夠解析泛型參數便可.

若是你的方法應該監聽多個事件或者你想用無參來定義它, 這個事件類型也能夠指定在註解上面. 以下所示:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

經過使用condition屬性能夠爲運行時添加過濾, 能夠定義一個SpEL表達式, 用來匹配特定事件調用該方法.

下面例子展現了咱們的notifier能夠重寫, 僅在content屬性等於my-enent的時候來調用:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每一個SpEL表達式依賴於專有上下文解析. 下表列出上下文能夠用的項目, 這些你能夠用來條件化處理事件:

表8 事件SpEL可用的元數據

名稱 位置 描述 示例
Event root object 其實是ApplicationEvent #root.event
Arguments array root object 用來調用目標的參數(數組) #root.args[0]
Argument name evaluation context 方法參數名字. 若是由於某種緣由,名稱不可用(例如由於debug信息),參數名稱也能夠基於#a<#arg>使用, 其中#arg表明參數索引(從0開始) #blEvent#a0 (你也能夠使用#p0#p<#arg>記號做爲別名)

注意#root.event提供了訪問底層事件的入口, 就算你的方法簽名實際指向發佈的任意對象.

若是你須要發佈另外一個事件的結果做爲事件, 你能夠改變方法簽名, 使其返回要發佈的事件, 以下:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

這個特性不支持異步事件監聽.

這個新方法爲每一個上面方法處理的BlackListEvent發佈了一個ListUpdateEvent事件. 若是須要發佈若干事件, 則須要返回事件的Collection.

異步監聽器

若是你想要一個特定的監聽器去異步處理事件, 你能夠重用標準的@Async支持. 以下:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

當使用異步事件時有以下限制:

  • 若是事件監聽器拋出Exception, 它將不會傳遞給調用者, 查看AsyncUncaughtExceptionHandler有更多細節.

  • 此類監聽器不能發送回覆. 若是你須要將處理結果做爲事件發佈, 須要手工將ApplicationEventPublisher注入.

有序監聽器

若是你須要一個監聽器在另外一個以前調用, 你能夠添加@Order註解到方法上, 以下:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}
泛型事件

你也能夠使用泛型深化定義事件結構. 考慮使用EntityCreatedEvent<T> 其中T是建立的真實實體. 例如, 你能夠僅僅經過EntityCreatedEventPerson建立事件監聽器:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

由於類型擦除, 只有在觸發的事件對事件偵聽器篩選的泛型參數進行轉述時, 此操做纔有效(也就是, 形如class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }).

在特定場景下, 若是全部事件容許相同的結構(如前面例子中的事件案例),這可能會變得至關乏味. 在相似示例中, 你能夠實現ResolvableTypeProvider來指導框架跨越運行時環境所能提供的. 以下:

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}

不滿是ApplicationEvent, 能夠是你發送的任意對象事件.

1.15.3 便捷訪問底層資源

爲了以最優來使用應用程序上下文的使用和理解,您應該使用Spring的Resource抽象(如Resource一章中所描述的)從新調整.

程序上下文是一個ResourceLoader,用來加載Resource對象. Resource本質上是個JDKjava.net.URL類的特性加強版本. 實際上, Resource的實現封裝了java.net.URL實例. Resource能以一種透明的方式獲取任何位置的底層資源, 包含類路徑, 系統路徑, 任何使用標準URL描述的地方, 還有其餘的變量. 若是資源路徑字符串是個沒有特殊前綴的字符串, 那麼這些資源的來源是特定的,而且適合於實際的應用程序上下文類型.

你能夠配置一個實現了指定藉口ResourceLoaderAware的bean部署到應用上下文, 用來自動在程序上下文初始化時做爲ResourceLoader傳入. 你也能夠暴露類型爲Resource的屬性, 用來訪問靜態資源. 他們像其餘屬性同樣被注入. 當bean被部署後, 你能夠像簡單的String路徑同樣指定這些Resource屬性, 並依賴自動從字符串轉化爲真實的Resource對象.

位置路徑或提供給ApplicationContext參數的路徑其實是資源字符串, 而且簡單的形式下, 將根據特定的上下文實現被合適的對待. 如ClassPathXmlApplicationContext將以類路徑對待一個簡單的路徑. 你能夠使用特定的前綴迫使定義從類路徑或URL加載, 無論真實的上下文是什麼.

1.15.4 Web程序便捷的ApplicationContext實例化

經過使用如ContextLoader你能夠建立ApplicationContext實例. 固然, 你也能夠使用ApplicationContext的其中一個實現類編程方式建立ApplicationContext.

你能夠經過ContextLoaderListener註冊ApplicationContext, 以下:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

監聽器檢查contextConfigLocation參數. 若是參數不存在, 默認使用/WEB-INF/applicationContext.xml. 當這個參數存在時, 監聽器經過預約義的字符(逗號,分號,空格)分割字符串並使用它們來做爲應用上下文查找的路徑. Ant風格的路徑模式也支持. 例如:/WEB-INF/*Context.xml(對於包含在WEB-INF目錄下的全部的以Context.xml結尾的文件)和/WEB-INF/**/*Context.xml(對於全部WEB-INF目錄下任何子目錄的文件).

1.15.5 部署SpringApplicationContext爲JavaEE RAR 文件

部署Spring的ApplicationContext爲JavaEE RAR 文件是可能的, 將封裝上下文和全部須要的類和庫JAR文件到一個JAVA EE RAR部署單元. 這等同於啓動了一個單獨的ApplicationContext(僅在Java EE 環境宿主), 可以訪問Java EE 服務器設施. RAR部署是一種更天然的替代方式去部署一個沒有頭文件的WAR文件, 實際上, 沒有任何HTTP入口的WAR文件常常用在Java EE環境中, 用來啓動一個ApplicationContext.

對於不須要HTTP入口但有消息端點和定時任務的程序上下文, RAR部署是理想化的. 這類上下文中的bean能使用程序服務器資源, 好比JTA事務管理和JNDI綁定JDBCDataSource實例以及JMSConnectionFactory實例, 而且也能註冊平臺的JMX服務器--都是經過Spring標準事務管理和JNDI還有JMX支持設施的. 經過Spring的TaskExecutor抽象, 應用組件也能夠與應用服務器JCAWorkManager交互.

關於RAR部署的更多配置細節能夠查看文檔SpringContextResourceAdapter類.

對於一個簡單的將ApplicationContext部署爲Java EE RAR 文件來講:

  1. 打包全部程序類到RAR文件(標準的JAR文件, 只是後綴不一樣). 添加所需的庫JAR到RAR架構的根下. 添加META-INF/ra.xml部署描述符(見javadocSpringContextResourceAdapter)還有相關的XML定義文件(典型的如META-INF/applicationContext.xml).

  2. 將結果RAR文件丟到應用程序服務器的部署路徑下.

此種部署單元是自包含的. 他們沒有暴露給外部組件, 甚至沒暴露給相同程序的其餘模塊. 與基於RAR的ApplicationContext交互一般經過它與其餘模塊共享的JMS目標發生. 基於RAR的ApplicationContext也可能定時做業或與文件系統種的文件交互. 若是它須要容許從外界同步訪問, 它能夠(舉例來講)處處RMI端點,端點是用來在相同機器上被其餘模塊使用的.

1.16 BeanFactory

BeanFactoryAPI提供了基本的Spring IoC容器功能. 它的專門的約定幾乎是用來與Spring其餘部分或相關三方框架集成, 在高級的容器GenericApplicationContext種, 它的DefaultListableBeanFactory實現是一個關鍵的代理.

BeanFactory和相關的接口(如BeanFactoryAware,InitializingBean,DisposableBean)是與其餘組件重要的集成點. 不須要任何註解或反射, 他們容許有效的在容器和它的組件間交互. 應用級別的bean可能使用相同的回調接口但通常更喜歡聲明式的DI, 無論經過註解或經過編程方式配置.

注意核心BeanFactoryAPI和它的DefaultListableBeanFactory實現不會假設配置格式或任何使用的組件註解. 全部這些調劑經過擴展(如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor)並在共享的做爲元數據呈現的BeanDefinition對象上操做. 這是使得Spring容器靈活可擴展的關鍵.

1.16.1 BeanFactory 或者 ApplicationContext?

本節解釋了BeanFactoryApplicationContext容器級別的不一樣和啓動時的含義.

無論你是否有理由, 你應該使用ApplicationContext, GenericApplicationContext和它的子類AnnotationConfigApplicationContext是爲自定義啓動更通常的實現. 這些是Spring核心容器通常目的而言主要的入口點: 配置文件加載, 觸發類路徑掃描, 編程註冊bean定義和註解類, 還有(從5.0開始)註冊函數式bean定義.

由於ApplicationContext包含了BeanFactory的全部功能, 它通常是更建議使用的, 除非有全部bean的處理控制的需求. 在ApplicationContext(如GenericApplicationContext實現)內, 按慣例幾種bean被探測(也就是, 經過名字或類型-特別的,post-processors), 而DefaultListableBeanFactory並不知道任何特定的bean.

對許多容器擴展特性來講, 如註解處理和AOP代理, BeanPostProcessor擴展點是關鍵. 若是你僅僅使用普通的DefaultListableBeanFactory, post-processors默認不會被探查和激活. 這種狀況多是疑惑的, 由於你的配置沒有任何錯誤. 固然此場景下 , 容器須要經過附加的設置徹底啓動.

下面列出了經過BeanFactoryApplicationContext接口和實現提供的特性.

表9 特性矩陣

Feature BeanFactory ApplicationContext
bean實例化/裝配
集成生命週期管理
BeanPostProcessor自動註冊
BeanFactoryPostProcessor自動註冊
MessageSource便捷訪問(對於內部)
內置ApplicationEvent發佈機制

顯式使用DefaultListableBeanFactory註冊一個post-processor, 你須要編程方式調用addBeanPostProcessor以下:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory

爲一個普通DefaultListableBeanFactory應用BeanFactoryPostProcessor, 你須要調用它的postProcessBeanFactory方法, 以下:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

兩個例子中, 顯式註冊步驟是沒必要要的, 這就是在Spring背景的程序中, ApplicationContext變量比DefaultListableBeanFactory更好用. 特別是當一個典型企業應用中依賴於BeanFactoryPostProcessorBeanPostProcessor實例進行擴展時.

AnnotationConfigApplicationContext有通用的post-processor註冊並可能在底層經過配置註解引入更多的processor, 如@EnableTransactionManagement. 在Spring基於註解的抽象級別, bean postprocessor的概念變成了一個微小的內部容器細節.

2. 資源

本章涵蓋了Spring如何處理資源以及在Spring中你如何與資源打交道的內容. 包含下面議題:

  • 簡介
  • Resource 接口
  • 內置 Resource 實現
  • ResourceLoader
  • ResourceLoaderAware接口
  • 做爲依賴項的Resource
  • 應用上下文和資源路徑

2.1 簡介

Java的標準java.net.URL類和用來處理多種URL前綴的標準處理器, 很不幸對於底層資源的訪問不是足夠的. 例如, 沒有標準的URL實現能用來訪問須要從類路徑下或者相關的ServletContext下的資源. 雖然能夠註冊爲特定的URL前綴的處理器(相似已經存在的處理http:前綴的處理器), 但通常比較複雜, 而且URL接口仍然缺少一些急需的功能, 例如檢查指向資源是否存在的方法.

2.2 資源接口

Spring的Resource接口意味着對於底層資源的抽象來講功能更豐富的接口. 下面列出了Resource接口定義:

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}

如同Resource接口定義的, 它擴展了InputStreamSource接口, 以下列出這個接口的定義:

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

一些重要的從Resource接口而來的方法是:

  • getInputStream() : 定位和打開資源, 返回從資源讀取的InputStream. 它期待每一個調用返回全新的InputStream. 調用者有義務關閉流.
  • exists(): 返回boolean指示這個資源其實是否物理上存在.
  • isOpen(): 返回boolean指示資源是否表現爲處理打開的流. 若是true,InputStream不能被屢次讀取,並且進讀取一次而後關閉以免資源泄漏. 返回false則一般對通常資源實現, 同時可能返回異常InputStreamResource.
  • getDescription(): 返回資源的描述, 當處理資源時用於錯誤的輸出. 一般時全限定文件名或資源的url.

其餘方法容許你獲取實際的表示資源的URLFile對象(若是底層實現是兼容的並支持此功能).

Spring自身使用Resource抽象擴展做爲許多方法簽名的參數類型. 其餘相同API中(如多個ApplicationContext實現)的方法使用String或簡單格式來建立Resource到上下文實現, 或者, 經過特殊的Stirng路徑上的前綴, 來使調用者指定必須建立使用的特定Resource.

Resource接口用在不少Spring或與Spring協做, 它自己是頗有用的工具類, 可用於你的代碼中, 用來訪問資源, 就算你代碼不知道或在乎Spring的其餘部分. 這使得你的代碼耦合到Spring中, 但它真的僅僅耦合了其中一小部分工具類, 這些類提供了更有用的URL的替換並能提供其餘相同目的的三方庫.

Resource抽象功能上不替換. 它作了封裝. 例如, UrlResource封裝了RUL並使用封裝的URL工做.

2.3 內置資源實現類

Spring包含以下Resource實現:

  • UrlResource
  • ClassPathResource
  • FileSystemResource
  • ServletContextResource
  • InputStreamResource
  • ByteArrayResource

2.3.1 UrlResource

UrlResource封裝了java.net.URL而且能夠用來訪問能夠用URL訪問的對象, 如文件,HTTP目標,FTP目標等.全部URL都有一個標準的String表現, 有標準化的前綴能夠用來代表URL類型. 包含file:來訪問文件路徑, http:訪問經過HTTP協議的資源, ftp:訪問FTP資源等.

UrlResource經過顯式使用UrlResource構造函數來建立, 但一般, 在你調用包含一個表示路徑的String參數API方法時就隱式建立了. 對於後者, JavaBeans PropertyEditor最終決定建立Resource哪一個類型.若是路徑包含熟知的前綴(如:classpath:), 他就爲這個前綴建立了一個特定的Resource. 那若是沒有可識別的前綴, 就假定它是一個標準的URL字符串並建立UrlResource.

2.3.2 ClassPathResource

這個類表示應該從類路徑包含的資源. 它使用線程上下文類加載器, 給定的類加載器,或者一個爲加載資源的類.

若是類路徑資源在文件系統, 不在包含在jar文件並無被解壓(被servlet引擎或環境)到文件系統中, 類路徑資源Resource實現支持做爲java.io.File解析. 爲處理這種狀況, Resource實現老是支持做爲java.net.URL解析.

ClassPathResource過顯式使用UrlResource構造函數來建立, 但一般, 在你調用包含一個表示路徑的String參數API方法時就隱式建立了. 對於後者, JavaBeans PropertyEditor識別出特定在字符串路徑上的前綴classpath:,並建立一個ClassPathResource.

2.3.3 FileSystemResource

這是一個對java.io.Filejava.nio.file.Path處理的Resource實現.支持做爲FileURL解析.

2.3.4 ServletContextResource

這是一個對ServletContext資源的Resource實現, ServletContext資源解析爲在相關web程序根路徑內的相對路徑.

支持流訪問或URL訪問, 但僅在web程序結構解壓而且資源以及存在於物理文件系統時容許java.io.File訪問. 無論是否解壓到文件系統或直接從Jar或其餘的相似數據庫的地方(這是可能的)訪問, 都依賴於Servlet容器.

2.3.5 InputStreamResource

InputStreamResource是對於一個給定InputStreamResource實現. 僅用於沒有合適的Resource實現時. 特別的, 更應該用ByteArrayResource或其餘基於文件的Resource實現.

相比其餘的Resource實現, 這是一個對以及打開資源的描述. 所以, isOpen()返回true. 若是你須要保持資源描述符或者你須要屢次讀取流就不要使用它.

2.3.6 ByteArrayResource

這是對給定字節數組的Resource實現. 它爲給定數組建立一個ByteArrayInputStream.

對於從任何給定的byte數組而不須要從單獨使用InputStreamResource時是頗有用的.

2.4 ResourceLoader

ResourceLoader接口被實現爲可以返回(加載)Resource的對象. 下面列出ResourceLoader接口定義:

public interface ResourceLoader {

    Resource getResource(String location);

}

全部程序上下文均實現了ResourceLoader接口. 因此, 全部程序上下文能夠包含Resource實例.

當你在特定的程序上下文調用getResource()方法時, 而且指定的位置路徑沒有特殊的前綴, 你就獲得一個特定於上下文的Resource類型. 例如, 假設下面代碼依賴於ClassPathXmlApplicationContext執行.

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

依賴於ClassPathXmlApplicationContext, 代碼返回一個ClassPathResource. 若是相同的方法依據FileSystemXmlApplicationContext實例執行, 他將返回FileSystemResource. 對於一個WebApplicationContext, 他將返回ServletContextResource. 每種上下文對應每種特定返回的對象.

結果, 你能夠使用一種恰當的特定上下文的風格加載資源.

另外一方面, 你可能也須要強制使用ClassPathResource, 無論上下文類型是什麼, 經過指定特定的classpath:前綴, 以下:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

類似的, 你能夠強制經過指定任意標準的java.net.URL前綴來使用UrlResource. 下面一對實例使用filehttp前綴:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");

下面表格簡要列出從String對象到Resource對象的轉化策略:

表10 資源字符串

前綴 示例 解釋
classpath: classpath:com/myapp/config.xml 從類路徑加載
file: file:///data/config.xml 以url格式從文件系統加載. 查看FileSystemResource附加說明
http: http://myserver/logo.png 從url加載
(none) /data/config.xml 依賴於底層ApplicationContext類型

2.5 ResourceLoaderAware接口

ResourceLoaderAware接口是特定的回調接口, 用來標識指望有ResourceLoader引用提供的組件. 下面展現了接口定義:

public interface ResourceLoaderAware {
    void setResourceLoader(ResourceLoader resourceLoader);
}

當一個類實現ResourceLoaderAware而且部署到應用上下文中(受Spring管理)時, 它就被上下文認做ResourceLoaderAware. 引用上下文接着調用setResourceLoader(ResourceLoader), 並將自身做爲參數傳入(記住, Spring全部應用上下文都實現了ResourceLoader接口).

由於ApplicationContext時一個ResourceLoader, bean也能夠實現ApplicationContextAware並使用現有的上下文直接加載資源. 儘管如此, 通常而言,若是這就是你想達到的目的, 最好使用特定的ResourceLoader接口(被認爲是個工具接口). 代碼將僅耦合到資源加載接口而不是整個SpringApplicationContext接口.

在程序組件中, 你也能夠依賴ResourceLoader自動裝配, 做爲ResourceLoaderAware實現的替代. 傳統的constructorbyType自動裝配模式(如在自動裝配協做者中描述的)能爲構造函數參數或setter方法提供ResourceLoader. 問了更多的靈活性(包括自動裝配域和多參數方法), 考慮使用基於註解的自動裝配特性. 這種狀況下,ResourceLoader自動裝配給 使用@Autowired註解的任何域,構造參數或方法參數. 更多信息參看:使用@Autowired.

2.6 Resource做爲依賴

若是一個bean將要經過某種動態方式斷定和提供資源路徑, 可能對於這個bean使用ResourceLoader接口加載資源就是有意義的. 例如, 考慮加載某類型的模版, 這種模版資源須要依賴於用戶角色. 若是資源是固定的, 那就能夠徹底排除ResourceLoader接口的使用, 暴露bean須要的的Resource屬性, 並指望他們被注入.

而後注入這些屬性的簡單之處在於,全部應用程序上下文都註冊並使用一個特殊的JavaBeans PropertyEditor,它能夠將字符串路徑轉換爲資源對象. 因此, 若是myBean有個類型爲Resource的模版屬性, 它能夠被配置爲一個簡單的字符串, 以下:

<bean id="myBean" class="...">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

注意資源路徑沒有前綴. 因此, 由於程序上下文將會被做爲ResourceLoader, 資源將經過ClassPathResource,FileSystemResource,ServletContextResource, 依賴於具體的上下文類型.

若是你須要強制使用特定的Resource類型,你能夠使用前綴. 下面兩個例子展現瞭如何強制一個ClassPathResourceUrlResource(後者被用來訪問文件系統文件):

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

2.7 應用上下文和資源路徑

本節涵蓋了如何建立有資源的上下文, 包含使用XML的捷徑, 如何使用通配符, 和其餘細節.

2.7.1 構建應用上下文

一個程序上下文構造器(對於一個特定的應用上下文類型)通常用一個字符串或字符串數組做爲資源的位置路徑, 如組成上下文的定義的XML文件.

當這個位置路徑不含有前綴時, 特定的Resource類型從這個路徑構建並加載bean定義依賴,並且時特定於上下文的. 例如, 考慮下面的例子, 建立了一個ClassPathXmlApplicationContext:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

bean的定義從類路徑加載, 由於使用了ClassPathResource. 再考慮下面的例子, 建立了FileSystemXmlApplicationContext:

ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");

如今bean定義從文件系統路徑加載(本例中, 相對於當前工做目錄).

注意特定的類路徑前綴或URL前綴覆蓋了默認的加載bean定義的Resource類型, 考慮下面例子:

ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

使用FileSystemXmlApplicationContext從類路徑加載定義. 雖然如此, 仍然是一個FileSystemXmlApplicationContext. 若是所以做爲ResourceLoader來使用, 任何沒有前綴的路徑都將被做爲文件系統路徑對待.

ClassPathXmlApplicationContext構建實例--捷徑

ClassPathXmlApplicationContext類暴露了不少構造函數來方便地初始化. 基本概念是, 你能夠僅僅提供一個包含XML文件名自己的字符串數組(沒有引導路徑信息),或者提供一個Class. ClassPathXmlApplicationContext接着從給定class獲取路徑信息.

有以下目錄結構:

com/
  foo/
    services.xml
    daos.xml
    MessengerService.class

下面例子展現了ClassPathXmlApplicationContext實例組合從名字爲services.xmldaos.xml 的文件中(在類路徑下)整合bean的定義初始化.

ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}, MessengerService.class);

查看ClassPathXmlApplicationContext文檔獲取更多構造函數的明細.

2.7.2 程序上下文構造函數資源路徑中的通配符

程序上下文構造函數中資源路徑能夠是簡單的路徑(如前面的例子), 每個都一對一映射到目標Resource, 或者相反, 可能存在指定的"classpath*:"前綴, 或者內部Ant風格的正則表達式(經過使用SpringPathMatcher工具匹配). 後面兩個都是有效的通配符.

這種機制的一種用處就是, 當你須要在一個組合風格的應用集中使用. 全部組件都能'publish'應用上下文定義片斷到熟知的路徑中, 而且當最終上下文使用相同的路徑前綴classpath*:建立時, 全部組合的片斷都自動被獲取.

注意, 構造函數中的通配符是特定於資源路徑並在構造時被解析. 它跟Resource類型自己沒有關係, 你不能使用classpath*:前綴去構建實際的Resource , 一個資源在某一時刻僅對應一個資源.

Ant風格的模式

路徑能夠包含Ant風格的模式, 以下:

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

若是路徑包含Ant風格的模式, 解析器遵循更爲複雜的處理過程去試圖解析通配符. 它爲路徑生成一個Resource資源到最近的非通配符片斷幷包含了一個URL. 若是URL不是jar:URL或者容器特定的變體(如WebLogic中的zip:, WebSphere中的wsjar等), 一個java.io.File包含進來並用於經過文件系統遍歷解析通配符. 若是是jar URL, 解析器也能夠得到到一個java.net.JarURLConnection並解析這個jar URL,而後遍歷jar文件的內容.

可移植的含義

若是特定的路徑已是一個文件URL(因基類ResourceLoader是文件系統而隱式的或顯式的), 通配符確保在一個徹底可移植的風格下工做.

若是指定的是一個類路徑, 解析器必須包含最近的非通配符路徑片斷URL, 這是經過調用Classloader.getResource()完成的. 由於這僅僅是一個路徑的節點(不是最終文件), 它其實是沒有明肯定義的一種URL. 實踐中, java.io.File一般表示目錄(類路徑資源解析爲文件路徑資源)或某jar URL(類路徑解析爲jar路徑). 一樣, 其中有種可移植的概念.

若是jar URL包含最近的非通配片斷, 解析器必須能獲取到java.net.JarURLConnection或者手工轉化爲jar URL, 從而能訪問jar的內容並解析通配符. 這在一些環境下會失敗, 咱們強烈建議特定環境下的jar資源通配符解析在特定環境下測試後再最終依賴它.

classpath*:前綴

當構建一個XML的應用上下文時, 位置字符串可能使用classpath*:前綴, 以下:

ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

特定的前綴指定了全部匹配到的類路徑資源必須被包含進來(內部, 這經過調用ClassLoader.getResources(…​)實現), 並最終合併到上下文定義中.

通配符類路徑依賴於底層類加載器的getResource()方法. 如今不少程序服務器提供了他們自身的加載器實現, 因此這種行爲可能有所差異, 特別是處理jar文件時. classpath*是否工做的一個簡單測試是使用加載器去加載類路徑下的一個jar文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>"). 嘗試這類測試, 當文件有相同的名稱但處於不一樣路徑下時. 萬一返回的結果不對, 檢查服務器文檔的設置, 這些設置可能會影響類加載器行爲.

你也能夠在路徑其餘部分中使用PathMatcher模式來組合classpath*:(例如, classpath*:META-INF/*-beans.xml). 這種狀況下, 解析策略至關簡單: ClassLoader.getResources()用來解析最後的非通配路徑片斷來獲取全部類加載器繼承中匹配的全部資源, 而後對於每種資源外, 對通配符子路徑使用前面描述的路徑匹配器解析策略。

通配符相關的其餘注意事項

注意classpath*:當組合使用Ant格式模式時, 只有在模式開始以前,至少一個根目錄才能可靠地工做,除非實際的目標文件駐留在文件系統中。這意味着相似classpath*:*.xml之類的模式可能不會從JAR文件的根檢索文件,而可能只從擴展目錄的根檢索文件。

Spring檢索類路徑條目的能力源於JDK的ClassLoader.getResources()方法,該方法只返回空字符串的文件系統位置(指示搜索的潛在根路徑)。Spring也在JAR文件中評估 URLClassLoader 加載器運行時配置和java.class.path清單,但這並不必定會致使可移植行爲。

對類路徑包的掃描要求在類路徑中存在相應的目錄條目。當您用Ant構建JAR時,不要激活JAR任務的僅文件開關。此外,類路徑目錄在某些環境(例如,JDK1.7.0_45及更高版本上的獨立應用程序)中可能不會根據安全策略公開(這須要在清單中設置「受信任的庫」)。參見http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

在JDK 9的模塊路徑(jigsaw)上,Spring的類路徑掃描通常按預期工做。將資源放在專用目錄中也是很是值得推薦的,避免了搜索JAR文件根級別的上述可移植性問題。

具備classpath:的Ant模式:若是要搜索的根包在多個類路徑位置中可用,則不能保證資源找到匹配的資源。考慮如下資源位置示例:

com/mycompany/package1/service-context.xml

如今考慮可能有人想要Ant模式中找到文件:

classpath:com/mycompany/**/service-context.xml

這樣的資源可能只位於一個位置,可是當使用前面的示例這樣的路徑試圖解析它時,解析器將關閉getResource("com/mycompany");返回的(第一個)url;。若是此基本包節點存在於多個類加載器位置,則可能不存在實際的結束資源。所以,在這種狀況下,您應該更喜歡使用具備相同Ant樣式的ClassPath*:模式,它搜索包含根包的全部類路徑位置。

FileSystemResource 附加說明

FileSystemResource沒有附加到FileSystemApplicationContext(也就是,當FileSystemApplicationContext不是真正的ResourceLoader)時, 它對待絕對路徑和相對路徑能按預期處理. 相對路徑相對於當前工做目錄,而絕對路徑相對於文件系統的根。

FileSystemApplicationContextResourceLoader時, 因向後兼容而有所變化. FileSystemApplicationContext強制全部依附的FileSystemResource實例以相對路徑方式對待路徑, 無論他們是否之前綴"/"開頭. 實踐中, 下面兩種方式是等同的:

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");

下面兩種方式也是等同的(就算區別開他們有意義, 由於一個是相對的一個是絕對的):

FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");

實踐中, 若是你須要絕對路徑, 你應該避免使用FileSystemResourceFileSystemXmlApplicationContext. 並經過file:前綴強制使用UrlResource. 以下展現瞭如何這麼作:

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");
相關文章
相關標籤/搜索