Spring 核心(第二部分)

1.5 Bean作用域

當您創建一個bean定義時,您將創建一個用於創建由該bean定義定義的類的實際實例的方法。bean定義是菜譜的想法很重要,因爲它意味着,與類一樣,您可以從一個菜譜創建多個對象實例。

您不僅可以控制要插入到由特定bean定義創建的對象中的各種依賴項和配置值,還可以控制由特定bean定義創建的對象的範圍。這種方法強大而靈活,因爲您可以選擇通過配置創建的對象的範圍,而不必在Java類級別上考慮對象的範圍。可以將bean定義爲部署在多種作用域中的一種。Spring框架支持6種作用域,其中4種只有在使用web感知的ApplicationContext時纔可用。您還可以創建自定義範圍。

下表描述了支持的範圍:

Scope Description

singleton

(默認)爲每個Spring IoC容器將單個bean定義作用於單個對象實例。

 

prototype

將單個bean定義作用於任意數量的對象實例。

request

將單個bean定義的範圍限定爲單個HTTP請求的生命週期。也就是說,每個HTTP請求都有自己的bean實例,這些實例是在單個bean定義的基礎上創建的。僅在可感知web的Spring應用程序上下文中有效。

session

將單個bean定義的範圍限定爲HTTP會話的生命週期。僅在可感知web的Spring應用程序上下文中有效。

application

將單個bean定義作用於ServletContext的生命週期。僅在可感知web的Spring應用程序上下文中有效。

websocket

將單個bean定義作用於WebSocket的生命週期。僅在可感知web的Spring應用程序上下文中有效。

從Spring 3.0開始,線程作用域可用,但默認情況下不註冊。有關更多信息,請參閱SimpleThreadScope的文檔。有關如何註冊此或任何其他自定義範圍的說明,請參閱Using a Custom Scope.。

1.5.1 單例的作用域

只管理一個單例bean的一個共享實例,所有對具有與該bean定義匹配的ID或ID的bean的請求都會導致Spring容器返回該特定bean實例。

換句話說,當您定義一個bean定義並將其定義爲一個單例對象時,Spring IoC容器只創建該bean定義定義的對象的一個實例。此單一實例存儲在此類單例bean的緩存中,該指定bean的所有後續請求和引用都將返回緩存的對象。下圖顯示了單例範圍的工作方式:

Spring的單例bean概念與四人組(GoF)模式書中定義的單例模式不同。單例對象對對象的作用域進行硬編碼,這樣每個類裝入器只能創建一個特定類的實例。Spring單例的範圍最好描述爲每個容器和每個bean。這意味着,如果您在單個Spring容器中爲特定類定義一個bean,那麼Spring容器將創建由該bean定義定義的類的一個且僅一個實例。單例範圍是Spring的默認範圍。要在XML中將bean定義爲單例,您可以定義如下例所示的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實例。也就是說,bean被注入到另一個bean中,或者您通過容器上的getBean()方法調用請求它。通常,您應該爲所有有狀態bean使用原型範圍,爲無狀態bean使用單例範圍。
下圖說明了Spring原型的作用域:

通常不將數據訪問對象配置爲原型,因爲典型的DAO不包含任何會話狀態。我們更容易重用單例圖的核心。)
下面的例子在XML中將bean定義爲原型:

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

與其他作用域不同,Spring不管理原型bean的完整生命週期。容器實例化、配置或以其他方式組裝原型對象並將其交給客戶機,而不需要該原型實例的進一步記錄。因此,儘管初始化生命週期回調方法在所有對象上都被調用,而與範圍無關,但是在原型的情況下,配置的銷燬生命週期回調不會被調用。客戶機代碼必須清理原型作用域的對象,並釋放原型bean持有的昂貴資源。爲了讓Spring容器釋放原型作用域bean所持有的資源,可以嘗試使用一個自定義bean後處理器,它持有一個需要清理的bean引用。

在某些方面,Spring容器在原型作用域bean方面的角色可以替代Java new操作符。所有超過那個點的生命週期管理都必須由客戶端處理。(有關Spring容器中bean生命週期的詳細信息,請參閱 Lifecycle Callbacks。)

1.5.3 具有原型bean依賴項的單例bean

當您使用依賴於原型bean的單例作用域bean時,請注意依賴項是在實例化時解析的。因此,如果您依賴地將一個原型作用域的bean注入到一個單例作用域的bean中,一個新的原型bean將被實例化,然後依賴地注入到單例bean中。prototype實例是惟一提供給單例作用域bean的實例。

但是,假設您希望單例作用域bean在運行時重複獲取原型作用域bean的新實例。您不能依賴地將一個原型作用域的bean注入到您的單例bean中,因爲這種注入只發生一次,當Spring容器實例化單例bean並解析和注入它的依賴項時。如果您需要在運行時多次使用原型bean的新實例,請參閱方法注入Method Injection

1.5.4 Request, Session, Application, and WebSocket作用域

只有在使用web感知的Spring ApplicationContext實現(如XmlWebApplicationContext)時,請求、會話、應用程序和websocket作用域纔可用。如果您將這些作用域與常規的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,則會拋出一個IllegalStateException,它會報錯一個未知的bean作用域。

初始化的Web配置

爲了在請求、會話、應用程序和websocket級別(web範圍的bean)上支持bean的作用域,需要在定義bean之前進行一些小的初始配置。(標準範圍:單例和原型不需要這個初始設置。)

如何完成這個初始設置取決於特定的Servlet環境。
如果您在Spring Web MVC中訪問作用域bean,實際上,在由Spring DispatcherServlet處理的請求中,不需要特殊的設置。DispatcherServlet已經公開了所有相關狀態。

如果您使用Servlet 2.5 web容器,並在Spring的DispatcherServlet之外處理請求(例如,在使用JSF或Struts時),您需要註冊org.springframe .web.context.request。RequestContextListener ServletRequestListener。對於Servlet 3.0+,這可以通過使用WebApplicationInitializer接口以編程方式實現。或者,對於較舊的容器,將以下聲明添加到web應用程序的web.xml文件中:

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

另外,如果偵聽器設置有問題,可以考慮使用Spring的RequestContextFilter。篩選器映射依賴於周圍的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請求對象綁定到服務該請求的線程。這使得在請求和會話範圍內的bean可以在調用鏈的更底層使用。

Request作用域

請考慮以下bean定義的XML配置:

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

通過爲每個HTTP請求使用LoginAction bean定義,Spring容器創建了LoginAction bean的新實例。也就是說,loginAction bean的作用域在HTTP請求級別。您可以隨意更改創建的實例的內部狀態,因爲從相同的loginAction bean定義創建的其他實例在狀態中看不到這些更改。它們是特定於個人請求的。當請求完成處理時,作用域爲請求的bean被丟棄。

在使用註釋驅動的組件或Java配置時,可以使用@RequestScope註釋將組件分配給請求範圍。下面的例子演示瞭如何做到這一點:

@RequestScope
@Component
public class LoginAction {
    // ...
}

Session作用域

請考慮以下bean定義的XML配置:

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

Spring容器通過爲單個HTTP會話的生命週期使用UserPreferences bean定義來創建UserPreferences bean的新實例。換句話說,userPreferences bean有效地限定在HTTP會話級別。與請求範圍內bean一樣,你可以改變內部狀態的實例創建儘可能多的你想要的,知道其他HTTP會話實例也使用相同的實例創建userPreferences bean定義看不到這些變化狀態,因爲他們是特定於一個單獨的HTTP會話。當HTTP會話最終被丟棄時,作用域爲該特定HTTP會話的bean也被丟棄。

在使用註釋驅動的組件或Java配置時,可以使用@SessionScope註釋將組件分配給會話範圍。

@SessionScope
@Component
public class UserPreferences {
    // ...
}

Application作用域

請考慮一下bean定義的以下XML配置:

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

通過爲整個web應用程序使用一次AppPreferences bean定義,Spring容器創建了AppPreferences bean的一個新實例。也就是說,appPreferences bean的作用域在ServletContext級別,並存儲爲一個常規的ServletContext屬性。這有點類似於彈簧單例bean,但在兩個重要方面不同:它是一個單例每ServletContext不是每春天ApplicationContext的(可能有幾個在任何給定的web應用程序),它實際上是暴露,因此可見ServletContext屬性。

在使用註釋驅動的組件或Java配置時,可以使用@ApplicationScope註釋將組件分配給應用程序範圍。下面的例子演示瞭如何做到這一點:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

將限定作用域的bean作爲依賴項

Spring IoC容器不僅管理對象(bean)的實例化,還管理協作者(或依賴項)的連接。如果您想將(例如)一個HTTP請求作用域的bean注入到另一個更長的作用域的bean中,您可以選擇注入一個AOP代理來代替作用域的bean。也就是說,您需要注入一個代理對象,它與作用域對象公開相同的公共接口,但也可以從相關作用域(如HTTP請求)檢索實際目標對象,並將方法調用委託給實際對象。

注意點:

你也可以在定義爲單例的bean之間使用<aop:scoped-proxy/>,然後引用通過一個可序列化的中間代理,因此能夠在反序列化時重新獲得目標單例bean。

當對範圍原型的bean聲明<aop:scoped-proxy/>時,共享代理上的每個方法調用都會導致創建一個新的目標實例,然後將調用轉發給該實例。

而且,範圍代理不是以生命週期安全的方式從較短範圍訪問bean的惟一方法。你也可以聲明你的注射點(也就是構造函數或setter參數或autowired的字段)作爲ObjectFactory < MyTargetBean >,允許getObject()調用來檢索當前實例對需求每次需要——沒有分別持有實例或存儲它。

作爲擴展的變體,您可以聲明ObjectProvider<MyTargetBean>,它提供了幾個額外的訪問變體,包括getIfAvailable和getIfUnique。

該方法的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://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/> //1
    </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>

//1 定義代理的行。

要創建這樣的代理,需要將一個子<aop:scoped-proxy/>元素插入到有作用域的bean定義中(參見選擇要創建的代理類型和基於XML模式的配置)。爲什麼在請求、會話和自定義範圍級別定義作用域的bean需要<aop:作用域代理/>元素?考慮一下下面的單例bean定義,並將它與您需要爲前面提到的作用域定義的內容進行對比(請注意,下面的userPreferences bean定義是不完整的):

<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會話範圍的bean的引用(userPreferences)。這裏的要點是userManager bean是單例的:它僅針對每個容器實例化一次,其依賴項(在本例中只有一個,即userPreferences bean)也僅注入一次。這意味着userManager bean只對完全相同的userPreferences對象(即最初注入它的對象)進行操作。

這不是您在將一個較短的作用域bean注入到一個較長的作用域bean時想要的行爲(例如,將一個HTTP會話作用域的協作bean作爲依賴項注入到單例bean中)。相反,您需要一個單一的userManager對象,並且對於HTTP會話的生存期,您需要一個特定於HTTP會話的userPreferences對象。因此,容器創建一個對象,該對象公開與UserPreferences類完全相同的公共接口(理想情況下是UserPreferences實例的對象),該對象可以從作用域機制(HTTP請求、會話,等等)獲取真正的UserPreferences對象。容器將此代理對象注入userManager bean,該bean不知道此UserPreferences引用是代理。在本例中,當UserManager實例調用依賴注入的UserPreferences對象上的方法時,它實際上是在調用代理上的方法。然後代理從HTTP會話中獲取真實的UserPreferences對象,並將方法調用委託給檢索到的真實的UserPreferences對象。

因此,在將請求和會話範圍的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代理只攔截公共方法調用!不要在這樣的代理上調用非公共方法。它們沒有被委託給實際作用域的目標對象。

或者,您可以配置Spring容器,爲這種作用域bean創建標準的基於JDK接口的代理,方法是爲<aop:作用域代理/>元素的代理目標類屬性的值指定false。使用基於JDK接口的代理意味着在應用程序類路徑中不需要額外的庫來影響這種代理。但是,這也意味着作用域bean的類必須實現至少一個接口,並且所有注入作用域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>

有關選擇基於類或基於接口的代理的更詳細信息,請參見 [aop-proxying]

1.5.5 自定義作用域

bean作用域機制是可擴展的。您可以定義自己的作用域,甚至可以重新定義現有的作用域,儘管後者被認爲是不好的實踐,並且您不能覆蓋內置的單例和原型作用域。

創建自定義範圍

要將您的自定義範圍集成到Spring容器中,您需要實現org.springframe .bean .factory.config.Scope接口,將在本節中描述。要了解如何實現您自己的作用域,請參閱Spring框架本身提供的作用域實現和作用域javadoc,這將更詳細地解釋您需要實現的方法。

作用域接口有四種方法來從作用域獲取對象,從作用域刪除對象,然後銷燬對象。
例如,會話範圍實現將返回會話範圍的bean(如果它不存在,則該方法將在將其綁定到會話以供將來引用後返回bean的新實例)。下面的方法從底層範圍返回對象:

Object get(String name, ObjectFactory<?> objectFactory)

例如,會話範圍實現從基礎會話中刪除會話範圍的bean。應該返回該對象,但是如果沒有找到具有指定名稱的對象,則可以返回null。下面的方法將對象從底層範圍中移除:

Object remove(String name)

以下方法註冊當範圍被銷燬或範圍內的指定對象被銷燬時應該執行的回調:

void registerDestructionCallback(String name, Runnable destructionCallback)

有關銷燬回調的更多信息,請參閱javadoc或Spring作用域實現。
下面的方法獲取底層作用域的對話標識符:

String getConversationId()

這個標識符對於每個範圍都是不同的。對於會話範圍的實現,此標識符可以是會話標識符。

使用自定義作用域

在編寫和測試一個或多個自定義範圍實現之後,您需要讓Spring容器知道您的新範圍。下面的方法是向Spring容器註冊新範圍的中心方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上聲明,該接口可通過Spring附帶的大多數具體ApplicationContext實現上的BeanFactory屬性獲得。
registerScope(..)方法的第一個參數是與範圍關聯的惟一名稱。Spring容器中此類名稱的例子有singleton和prototype。registerScope(..)方法的第二個參數是您希望註冊和使用的自定義範圍實現的實際實例。

假設您編寫了自定義範圍實現,然後在下一個示例中註冊它。

注意:下一個示例使用SimpleThreadScope,它包含在Spring中,但默認情況下不註冊。對於您自己的自定義範圍實現,說明是相同的。

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然後,您可以創建遵循自定義範圍的範圍規則的bean定義,如下所示:
<bean id="..." class="..." scope="thread">

使用自定義範圍實現,您不侷限於範圍的程序性註冊。您還可以通過使用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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://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/>時,受作用域限制的是工廠bean本身,而不是從getObject()返回的對象。

1.6 自定義Bean的性質

Spring框架提供了許多接口,您可以使用它們來定製bean的性質。本節將它們分組如下:

1.6.1. Lifecycle Callbacks

爲了與容器對bean生命週期的管理進行交互,您可以實現Spring InitializingBean和一次性bean接口。容器爲前者調用afterPropertiesSet(),爲後者調用destroy(),以便bean在初始化和銷燬bean時執行某些操作。

注意:

JSR-250 @PostConstruct和@PreDestroy註釋通常被認爲是在現代Spring應用程序中接收生命週期回調的最佳實踐。使用這些註釋意味着您的bean沒有耦合到特定於spring的接口。有關詳細信息,請參見[beans-post - construct-and- pre- annotations]。
如果您不希望使用JSR-250註釋,但仍然希望消除耦合,那麼可以考慮init-method和destroy-method bean定義元數據。

在內部,Spring框架使用BeanPostProcessor實現來處理它能找到並調用適當方法的任何回調接口。如果您需要自定義特性或Spring默認不提供的其他生命週期行爲,您可以自己實現BeanPostProcessor。有關更多信息,請參見容器擴展點。

除了初始化和銷燬回調之外,spring管理的對象還可以實現生命週期接口,以便這些對象可以參與啓動和關閉過程,這是由容器自身的生命週期驅動的。
生命週期回調接口將在本節中描述。

Initialization Callbacks

org.springframework.beans.factory.InitializingBean接口允許bean在容器設置了bean上所有必需的屬性之後執行初始化工作。InitializingBean接口指定一個方法:

void afterPropertiesSet() throws Exception;

我們建議您不要使用InitializingBean接口,因爲它不必要地將代碼耦合到Spring。另外,我們建議使用@PostConstruct註釋或指定POJO初始化方法。對於基於xml的配置元數據,可以使用init-method屬性指定具有void無參數簽名的方法的名稱。使用Java配置,您可以使用@Bean的initMethod屬性。看到[beans-java-lifecycle-callbacks]。考慮下面的例子:

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

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

然而,前面兩個示例中的第一個並沒有將代碼耦合到Spring。

Destruction Callbacks

org.springframework.beans.factory.DisposableBean 當包含它的容器被銷燬時,可處置bean接口讓bean獲得回調。可處置bean接口指定了一個方法:

void destroy() throws Exception;

我們建議您不要使用DisposableBean回調接口,因爲它不必要地將代碼與Spring綁定在一起。另外,我們建議使用@PreDestroy註釋或指定bean定義支持的泛型方法。使用基於xml的配置元數據,您可以在<bean/>上使用destroy-method屬性。通過Java配置,您可以使用@Bean的destroyMethod屬性。看到[beans-java-lifecycle-callbacks]。考慮以下定義:

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

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

以上定義與以下定義的效果幾乎完全相同:

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

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

然而,前面兩個定義中的第一個並沒有將代碼耦合到Spring。

注意:您可以爲<bean>元素的destroy-method屬性指定一個特殊的(推斷)值,該值指示Spring自動檢測特定bean類上的公共關閉或關閉方法。(任何實現java.lang的類。AutoCloseable或io。因此,可關閉將匹配。)您還可以在<beans>元素的Default - Destroy -method屬性上設置這個特殊的(推斷出來的)值,以便將此行爲應用於整個bean集(請參閱缺省初始化和銷燬方法)。注意,這是Java配置的默認行爲。

默認初始化和銷燬方法

當您編寫不使用特定於spring的InitializingBean和DisposableBean回調接口的初始化和銷燬方法回調時,您通常會編寫具有init()、initialize()、dispose()等名稱的方法。理想情況下,這樣的生命週期回調方法的名稱在整個項目中是標準化的,這樣所有開發人員都可以使用相同的方法名稱並確保一致性。

您可以將Spring容器配置爲“look”指定的初始化並銷燬每個bean上的回調方法名。這意味着,作爲應用程序開發人員,您可以編寫應用程序類並使用名爲init()的初始化回調,而不必爲每個bean定義配置init-method="init"屬性。在創建bean時,Spring IoC容器調用該方法(並根據前面描述的標準生命週期回調契約)。該特性還強制對初始化和銷燬方法回調使用一致的命名約定。

假設您的初始化回調方法命名爲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.");
        }
    }
}

然後你可以在一個類似如下的bean中使用這個類:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

頂級<beans/>元素屬性上的default-init-method屬性導致Spring IoC容器將bean類上一個名爲init的方法識別爲初始化方法回調。在創建和組裝bean時,如果bean類有這樣的方法,則在適當的時候調用它。

您可以通過在頂級<beans/>元素上使用default-destroy-method屬性來配置destroy方法回調(即在XML中)。
現有的bean類已經有了根據約定命名的回調方法,您可以通過使用<bean/>本身的init-method和destroy-method屬性指定(在XML中)方法名來覆蓋默認值。

Spring容器保證在向bean提供所有依賴項後立即調用已配置的初始化回調。因此,在原始bean引用上調用初始化回調,這意味着AOP攔截器等還沒有應用到bean上。首先完全創建一個目標bean,然後應用帶有攔截器鏈的AOP代理(例如)。如果目標bean和代理是單獨定義的,那麼您的代碼甚至可以繞過代理與原始目標bean交互。因此,將攔截器應用於init方法將是不一致的,因爲這樣做將把目標bean的生命週期耦合到它的代理或攔截器,並在代碼直接與原始目標bean交互時留下奇怪的語義。

Combining Lifecycle Mechanisms

從spring2.5開始,你有三個控制bean生命週期行爲的選項:

  • InitializingBean和一次性bean回調接口
  • 自定義init()和destroy()方法
  • @PostConstruct和@PreDestroy註釋。您可以組合這些機制來控制給定的bean。

注意:如果爲bean配置了多個生命週期機制,並且每個機制都配置了不同的方法名,那麼每個配置的方法將按照本說明後面列出的順序執行。但是,如果爲多個生命週期機制配置了相同的方法名(例如,初始化方法的init()),則該方法將執行一次,如前一節所述。

使用不同的初始化方法爲同一個bean配置多個生命週期機制,調用方法如下:

  1. 方法註釋@PostConstruct
  2. InitializingBean回調接口定義的afterPropertiesSet()
  3. 自定義配置的init()方法

銷燬方法的調用順序相同:

  1. 方法註釋@PreDestroy
  2. 銷燬()是由拋出bean回調接口定義的
  3. 自定義配置的destroy()方法

啓動和關閉回調

生命週期接口定義了任何對象的基本方法,它有自己的生命週期需求(如啓動和停止一些後臺進程):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何spring管理對象都可以實現生命週期接口。然後,當ApplicationContext本身接收到啓動和停止信號(例如,運行時的停止/重啓場景)時,它將這些調用級聯到該上下文中定義的所有生命週期實現。它通過委託給一個LifecycleProcessor來做到這一點,如下面的清單所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

注意,LifecycleProcessor本身就是生命週期接口的擴展。它還添加了另外兩種方法來對刷新和關閉的上下文做出響應。

注意:

請注意,常規的org.springframe .context。生命週期接口是顯式啓動和停止通知的普通契約,並不意味着在上下文刷新時自動啓動。對於特定bean的自動啓動(包括啓動階段)的細粒度控制,請考慮實現org.springframe .context。SmartLifecycle代替。

另外,請注意停止通知不能保證在銷燬之前發出。在常規關閉時,所有生命週期bean在傳播常規銷燬回調之前首先收到一個停止通知。但是,在上下文的生存期或中止的刷新嘗試中進行熱刷新時,只調用destroy方法。

啓動和關閉調用的順序可能很重要。如果任何兩個對象之間存在“依賴關係”,依賴方在依賴後開始,在依賴前停止。然而,有時,直接依賴關係是未知的。您可能只知道某種類型的對象應該先於另一種類型的對象啓動。在這些情況下,SmartLifecycle接口定義了另一個選項,即在其超級接口上定義的getPhase()方法。下面的清單顯示了階段接口的定義:

public interface Phased {

    int getPhase();
}

下面的清單顯示了SmartLifecycle接口的定義:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

啓動時,具有最低相位的對象首先啓動。停止時,執行相反的順序。因此,一個實現SmartLifecycle的對象,它的getPhase()方法返回Integer。MIN_VALUE將是第一個開始和最後一個停止的值。在頻譜的另一端,相位值爲整數。MAX_VALUE將指示該對象應該最後啓動並首先停止(可能是因爲它取決於要運行的其他進程)。在考慮phase值時,同樣重要的是要知道,對於任何沒有實現SmartLifecycle的“正常”生命週期對象,默認的phase是0。因此,任何負相位值都表示一個對象應該在那些標準組件之前啓動(在它們之後停止)。對於任何正相位值,情況正好相反。

SmartLifecycle定義的stop方法接受回調。任何實現都必須在該實現的關閉過程完成後調用該回調的run()方法。這在必要時支持異步關閉,因爲LifecycleProcessor接口的默認實現DefaultLifecycleProcessor會等待每個階段中的對象組的超時值來調用回調。默認的每個階段超時時間是30秒。通過在上下文中定義一個名爲lifecycleProcessor的bean,可以覆蓋默認的lifecycle processor實例。如果您只想修改超時,定義以下內容就足夠了:

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

如前所述,LifecycleProcessor接口還定義了用於刷新和關閉上下文的回調方法。後者驅動關閉進程,就好像已經顯式地調用了stop(),但它是在上下文關閉時發生的。另一方面,“refresh”回調啓用了SmartLifecycle bean的另一個特性。當上下文被刷新時(在所有對象被實例化和初始化之後),回調被調用。此時,默認的生命週期處理器將檢查每個SmartLifecycle對象的isAutoStartup()方法返回的布爾值。如果爲真,則在該點啓動該對象,而不是等待顯式調用上下文的start()方法(與上下文刷新不同,對於標準上下文實現,上下文啓動不會自動發生)。正如前面所述,階段值和任何“依賴”關係決定啓動順序。

在非web應用程序中優雅地關閉Spring IoC容器

注意:

本節僅適用於非web應用程序。Spring的基於web的ApplicationContext實現已經有了適當的代碼,可以在相關web應用程序關閉時優雅地關閉Spring IoC容器。

如果您在非web應用程序環境(例如,在富客戶機桌面環境中)中使用Spring的IoC容器,請向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 ApplicationContextAware和BeanNameAware

當ApplicationContext創建一個實現org.springframe .context的對象實例時。ApplicationContext接口,實例提供了該ApplicationContext的引用。下面的清單顯示了applicationcontext - ware接口的定義:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以通過編程方式操作創建它們的ApplicationContext,方法是通過ApplicationContext接口,或者通過將引用轉換爲該接口的一個已知子類(例如ConfigurableApplicationContext,它公開了額外的功能)。一種用途是通過編程檢索其他bean。有時這種能力是有用的。但是,一般來說,您應該避免使用它,因爲它將代碼與Spring耦合在一起,並且不遵循控制反轉的風格,即將協作者作爲屬性提供給bean。ApplicationContext的其他方法提供對文件資源的訪問、發佈應用程序事件和訪問MessageSource。這些附加的特性在[上下文介紹]中進行了描述。

自動裝配是獲取ApplicationContext引用的另一種選擇。傳統的構造函數和byType自動裝配模式(如自動裝配協作者中所述)可以分別爲構造函數參數或setter方法參數提供ApplicationContext類型的依賴項。爲了獲得更大的靈活性,包括自動裝配字段和多個參數方法的能力,可以使用基於註釋的自動裝配特性。如果你這樣做了,ApplicationContext就會被自動拖放到一個字段、構造函數參數或者方法參數中,如果這個字段、構造函數或者方法帶有@Autowired註解,那麼這個參數就會期望ApplicationContext類型。更多信息,請參見使用@Autowired。

當ApplicationContext創建一個實現org.springframe .bean .factory的類時。類的關聯對象定義中定義的名稱的引用。下面的清單顯示了BeanNameAware接口的定義:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回調是在填充普通bean屬性之後,但在初始化回調(如InitializingBean、afterPropertiesSet或自定義init-method)之前調用的。

1.6.3 其他Aware接口

除了applicationcontext ware和BeanNameAware(前面討論過)之外,Spring還提供了廣泛的可識別回調接口,讓bean向容器表明它們需要某種基礎設施依賴。通常,名稱表示依賴項類型。下表總結了最重要的Aware接口:

. Aware interfaces
Name Injected Dependency Explained in…​

ApplicationContextAware

申明ApplicationContext.

ApplicationContextAware and BeanNameAware

ApplicationEventPublisherAware

Event publisher of the enclosing ApplicationContext.

[context-introduction]

BeanClassLoaderAware

Class loader used to load the bean classes.

Instantiating Beans

BeanFactoryAware

Declaring BeanFactory.

ApplicationContextAware and BeanNameAware

BeanNameAware

Name of the declaring bean.

ApplicationContextAware and BeanNameAware

BootstrapContextAware

Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContextinstances.

JCA CCI

LoadTimeWeaverAware

Defined weaver for processing class definition at load time.

[aop-aj-ltw]

MessageSourceAware

Configured strategy for resolving messages (with support for parametrization and internationalization).

[context-introduction]

NotificationPublisherAware

Spring JMX notification publisher.

Notifications

ResourceLoaderAware

Configured loader for low-level access to resources.

[resources]

ServletConfigAware

Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext.

Spring MVC

ServletContextAware

Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext.

Spring MVC

再次注意,使用這些接口將您的代碼綁定到Spring API,並且不遵循控制反轉樣式。因此,我們建議將它們用於需要對容器進行編程訪問的基礎設施bean。

1.7 Bean定義繼承

bean定義可以包含很多配置信息,包括構造函數參數、屬性值和特定於容器的信息,比如初始化方法、靜態工廠方法名等等。子bean定義從父定義繼承配置數據。子定義可以根據需要覆蓋某些值或添加其他值。使用父bean和子bean定義可以節省大量輸入。實際上,這是模板的一種形式。

如果您以編程方式使用ApplicationContext接口,則子bean定義由ChildBeanDefinition類表示。大多數用戶在這個級別上不使用它們。相反,它們在類(如ClassPathXmlApplicationContext)中聲明式地配置bean定義。在使用基於xml的配置元數據時,可以通過使用父屬性指定父bean作爲該屬性的值來指示子bean定義。下面的例子演示瞭如何做到這一點:

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

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

// 1 父屬性。

如果沒有指定,則子bean定義使用父定義中的bean類,但也可以覆蓋它。在後一種情況下,子bean類必須與父類兼容(也就是說,它必須接受父類的屬性值)。

子bean定義繼承父bean的作用域、構造函數參數值、屬性值和方法覆蓋,並具有添加新值的選項。指定的任何範圍、初始化方法、銷燬方法或靜態工廠方法設置將覆蓋相應的父設置。

其餘的設置總是取自子定義:依賴、自動裝配模式、依賴項檢查、單例和惰性初始化。

前面的示例通過使用抽象屬性顯式地將父bean定義標記爲抽象。如果父定義沒有指定類,則需要顯式地將父bean定義標記爲抽象,如下面的示例所示:

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

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

父bean不能單獨實例化,因爲它是不完整的,而且它也被顯式地標記爲抽象。當定義是抽象的時,它只能作爲作爲子定義的父定義的純模板bean定義使用。嘗試單獨使用這樣一個抽象的父bean,方法是將它引用爲另一個bean的ref屬性,或者使用父bean ID執行顯式的getBean()調用,這會返回一個錯誤。類似地,容器的內部預實例化esingletons()方法忽略定義爲抽象的bean定義。

注意:默認情況下,ApplicationContext預先實例化所有單例對象。因此,它是重要的(至少對單例bean),如果你有一個(父)bean定義你只打算使用作爲模板,這個定義指定了一個類,您必須確保設置抽象屬性爲true,否則應用程序上下文會(試圖)pre-instantiate抽象的bean。

1.8 容器擴展點

通常,應用程序開發人員不需要繼承ApplicationContext實現類。相反,可以通過插入特殊集成接口的實現來擴展Spring IoC容器。下面幾節將介紹這些集成接口。

1.8.1 通過使用BeanPostProcessor定製bean

BeanPostProcessor接口定義了回調方法,您可以實現這些回調方法來提供自己的(或覆蓋容器的默認)實例化邏輯、依賴項解析邏輯等等。如果希望在Spring容器完成實例化、配置和初始化bean之後實現一些自定義邏輯,可以插入一個或多個自定義BeanPostProcessor實現。

您可以配置多個BeanPostProcessor實例,並且可以通過設置order屬性來控制這些BeanPostProcessor實例的執行順序。只有在BeanPostProcessor實現有序接口時才能設置此屬性。如果您編寫自己的BeanPostProcessor,也應該考慮實現有序接口。有關詳細信息,請參見BeanPostProcessor和有序接口的javadoc。參見BeanPostProcessor實例的程序性註冊說明。

注意:BeanPostProcessor實例操作bean(或對象)實例。也就是說,Spring IoC容器實例化一個bean實例,然後BeanPostProcessor實例執行它們的工作。

BeanPostProcessor實例的作用域是每個容器。這僅在使用容器層次結構時才相關。如果您在一個容器中定義一個BeanPostProcessor,那麼它只對該容器中的bean進行後處理。換句話說,在一個容器中定義的bean不會由在另一個容器中定義的BeanPostProcessor進行後處理,即使兩個容器都是相同層次結構的一部分。

要更改實際的bean定義(即定義bean的藍圖),您需要使用BeanFactoryPostProcessor,如用BeanFactoryPostProcessor自定義配置元數據中所述。

org.springframework.beans.factory.config。BeanPostProcessor接口正好由兩個回調方法組成。當這樣一個類註冊爲後處理器的容器,每個容器創建bean實例,後處理器從容器之前得到一個回調容器初始化方法(如InitializingBean.afterPropertiesSet()或任何宣佈init方法),任何bean初始化後回調。後處理器可以對bean實例採取任何操作,包括完全忽略回調。bean後處理器通常檢查回調接口,或者使用代理包裝bean。爲了提供代理包裝邏輯,一些Spring AOP基礎設施類被實現爲bean後處理器。

ApplicationContext自動檢測在實現BeanPostProcessor接口的配置元數據中定義的任何bean。ApplicationContext將這些bean註冊爲後處理器,以便以後在創建bean時調用它們。Bean後置處理器可以與任何其他Bean以相同的方式部署在容器中。

注意,當在配置類上使用@Bean工廠方法聲明BeanPostProcessor時,工廠方法的返回類型應該是實現類本身,或者至少是org.springframe .bean .factory.config。BeanPostProcessor接口,清楚地指示該bean的後處理器特性。否則,ApplicationContext不能在完全創建它之前通過類型自動檢測它。由於爲了應用於上下文中其他bean的初始化,需要儘早實例化BeanPostProcessor,所以這種早期類型檢測非常重要。

注意:

以編程方式註冊BeanPostProcessor實例
雖然推薦的BeanPostProcessor註冊方法是通過ApplicationContext自動檢測(如前所述),但是您可以通過使用addBeanPostProcessor方法以編程方式針對ConfigurableBeanFactory註冊它們。當您需要在註冊之前計算條件邏輯,甚至在層次結構的上下文中複製bean post處理器時,這可能很有用。但是,請注意,以編程方式添加的BeanPostProcessor實例並不遵循有序接口。在這裏,註冊的順序決定了執行的順序。還要注意,以編程方式註冊的BeanPostProcessor實例總是在通過自動檢測註冊的實例之前處理,而不考慮任何顯式的順序。

注意:

BeanPostProcessor實例和AOP自動代理
實現BeanPostProcessor接口的類是特殊的,容器以不同的方式對待它們。它們直接引用的所有BeanPostProcessor實例和bean在啓動時實例化,作爲ApplicationContext的特殊啓動階段的一部分。接下來,以排序的方式註冊所有BeanPostProcessor實例,並將其應用於容器中所有後續的bean。因爲AOP自動代理是作爲一個BeanPostProcessor本身來實現的,所以無論是BeanPostProcessor實例還是它們直接引用的bean都不適合自動代理,因此,沒有將方面編織到它們之中。

對於任何這樣的bean,您應該看到一條信息日誌消息:bean someBean不適合被所有BeanPostProcessor接口處理(例如:不適合自動代理)。

如果通過使用autowiring或@Resource(可能會回到autowiring)將bean連接到BeanPostProcessor中,Spring可能會在搜索類型匹配依賴項候選項時訪問意外的bean,因此,使它們不適合自動代理或其他類型的bean後處理。例如,如果您有一個帶有@Resource註釋的依賴項,其中字段或setter名稱與bean的聲明名稱不直接對應,並且沒有使用name屬性,那麼Spring將訪問其他bean,以便根據類型匹配它們。

下面的示例演示如何在ApplicationContext中編寫、註冊和使用BeanPostProcessor實例。

例子:Hello World, beanpostprocessor風格

第一個例子演示了基本用法。該示例顯示了一個自定義BeanPostProcessor實現,該實現在容器創建每個bean時調用toString()方法,並將結果字符串打印到系統控制檯。
下面的清單顯示了自定義BeanPostProcessor實現類定義:

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

下面的bean元素使用了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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://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一樣依賴注入。(前面的配置還定義了一個由Groovy腳本支持的bean。有關Spring動態語言支持的詳細信息,請參閱“ Dynamic Language Support”一章。

以下Java應用程序運行上述代碼和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

前一個應用程序的輸出類似如下:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

例子:RequiredAnnotationBeanPostProcessor

將回調接口或註釋與自定義BeanPostProcessor實現結合使用是擴展Spring IoC容器的常見方法。一個例子是Spring的RequiredAnnotationBeanPostProcessor——這是一個隨Spring發行版一起發佈的BeanPostProcessor實現,它確保使用(任意)註釋標記的bean上的JavaBean屬性實際上(配置爲)被注入了一個值。

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

我們查看的下一個擴展點是org.springframe .bean .factory.config. beanfactorypostprocessor。此接口的語義與BeanPostProcessor的語義相似,但有一個主要區別:BeanFactoryPostProcessor操作bean配置元數據。也就是說,Spring IoC容器允許BeanFactoryPostProcessor讀取配置元數據,並可能在容器實例化除BeanFactoryPostProcessor實例之外的任何bean之前更改它。

您可以配置多個BeanFactoryPostProcessor實例,並且可以通過設置order屬性來控制這些BeanFactoryPostProcessor實例的運行順序。但是,只有在BeanFactoryPostProcessor實現有序接口時才能設置此屬性。如果您編寫自己的BeanFactoryPostProcessor,也應該考慮實現有序接口。有關更多細節,請參見BeanFactoryPostProcessor和有序接口的javadoc。

注意:如果您想要更改實際的bean實例(即,從配置元數據創建的對象),那麼您需要使用BeanPostProcessor(在前面通過使用BeanPostProcessor定製bean中描述過)。雖然在BeanFactoryPostProcessor中使用bean實例在技術上是可行的(例如,通過使用BeanFactory.getBean()),這樣做會導致過早實例化bean,違反標準容器生命週期。這可能會導致負面的副作用,比如繞過bean的後處理。

而且,BeanFactoryPostProcessor實例的作用域是每個容器。這僅在使用容器層次結構時才相關。如果您在一個容器中定義一個BeanFactoryPostProcessor,那麼它只應用於該容器中的bean定義。一個容器中的Bean定義不會由另一個容器中的BeanFactoryPostProcessor實例進行後處理,即使兩個容器都是相同層次結構的一部分。

當在ApplicationContext中聲明bean工廠後處理器時,它將自動執行,以便將更改應用於定義容器的配置元數據。Spring包含許多預定義的bean工廠後處理程序,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您還可以使用自定義BeanFactoryPostProcessor—例如,註冊自定義屬性編輯器。

ApplicationContext自動檢測部署到其中的實現BeanFactoryPostProcessor接口的任何bean。它在適當的時候使用這些bean作爲bean factory的後處理器。您可以像部署任何其他bean一樣部署這些後處理器bean。

與beanpostprocessor一樣,您通常不希望將beanfactorypostprocessor配置爲延遲初始化。如果沒有其他bean引用bean(工廠)後處理器,則根本不會實例化後處理器。因此,將其標記爲延遲初始化將被忽略,即使在<beans />元素的聲明中將default-lazy-init屬性設置爲true, Bean(工廠)後處理器也將被急切地實例化。

例如:類名替換PropertySourcesPlaceholderConfigurer

通過使用標準的Java屬性格式,您可以使用PropertySourcesPlaceholderConfigurer將屬性值從bean定義外部化到一個單獨的文件中。這樣做使部署應用程序的人員能夠自定義特定於環境的屬性,如數據庫url和密碼,而不需要修改主XML定義文件或容器文件的複雜性或風險。

考慮以下基於xml的配置元數據片段,其中定義了具有佔位符值的數據源:

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

該示例顯示了從外部屬性文件配置的屬性。在運行時,PropertySourcesPlaceholderConfigurer應用於替換數據源的某些屬性的元數據。要替換的值被指定爲表單${property-name}的佔位符,它遵循Ant和log4j以及JSP EL樣式。

實際值來自另一個標準Java屬性格式的文件:

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

因此,$ {jdbc.username名}字符串在運行時被替換爲值'sa',相同的情況也適用於其他與屬性文件中的鍵匹配的佔位符值。PropertySourcesPlaceholderConfigurer檢查bean定義的大多數屬性和屬性中的佔位符。此外,您可以自定義佔位符前綴和後綴。

通過Spring 2.5中引入的上下文命名空間,您可以使用專用的配置元素來配置屬性佔位符。您可以在location屬性中以逗號分隔的列表的形式提供一個或多個位置,如下面的示例所示:

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

PropertySourcesPlaceholderConfigurer不僅在您指定的屬性文件中查找屬性。默認情況下,如果在指定的屬性文件中找不到屬性,它會檢查Spring環境屬性和常規Java系統屬性。

注意:您可以使用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時,即在非lazy-init bean的ApplicationContext的預實例化esingletons()階段,bean的解析將失敗。

例子:PropertyOverrideConfigurer

另一個bean工廠後處理器PropertyOverrideConfigurer類似於PropertySourcesPlaceholderConfigurer,但與後者不同,原始定義可以有缺省值,也可以沒有bean屬性的值。如果覆蓋的屬性文件沒有某個bean屬性的條目,則使用默認的上下文定義。

注意,bean定義不知道被覆蓋,所以從XML定義文件中不能立即看出使用了覆蓋配置程序。如果有多個PropertyOverrideConfigurer實例爲同一個bean屬性定義不同的值,由於覆蓋機制,最後一個實例勝出。

屬性文件配置行採用以下格式:

beanName.property=value

下面的清單顯示了格式的一個例子:

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

此示例文件可與容器定義一起使用,容器定義包含一個名爲dataSource的bean,該bean具有驅動程序和url屬性。
也支持複合屬性名,只要路徑的每個組件(被覆蓋的最終屬性除外)都是非空的(可能是由構造函數初始化的)。在下面的例子中,tom bean的fred屬性的bob屬性的sammy屬性被設置爲標量值123:

tom.fred.bob.sammy=123

注意:指定的重寫值總是文字值。它們不會被轉換成bean引用。當XML bean定義中的原始值指定一個bean引用時,也適用這種約定。

通過在Spring 2.5中引入上下文命名空間,可以使用專用的配置元素配置屬性覆蓋,如下面的示例所示:

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

1.8.3 使用FactoryBean定製實例化邏輯

您可以實現org.springframe .bean .factory。FactoryBean接口用於本身就是工廠的對象。
FactoryBean接口是一個可插入Spring IoC容器實例化邏輯的點。如果您有複雜的初始化代碼,用Java表示比(可能的)冗長的XML更好,那麼您可以創建自己的FactoryBean,在該類中編寫複雜的初始化,然後將定製的FactoryBean插入容器中。

FactoryBean接口提供了三種方法:

  • 對象getObject():返回工廠創建的對象的實例。實例可以共享,這取決於這個工廠返回的是單例還是原型。
  • 如果這個FactoryBean返回單例,則返回true;否則返回false。
  • 類getObjectType():返回getObject()方法返回的對象類型,如果類型事先不知道,則返回null。

FactoryBean概念和接口在Spring框架的許多地方都使用。FactoryBean接口的50多個實現都附帶了Spring本身。

當您需要向容器請求實際的FactoryBean實例本身而不是它所生成的bean時,在調用ApplicationContext的getBean()方法時,在bean的id前面加上與符號(&)。因此,對於id爲myBean的給定FactoryBean,在容器上調用getBean(“myBean”)將返回FactoryBean的產品,而調用getBean(“&myBean”)將返回FactoryBean實例本身。

1.9 基於註解的容器配置

對於配置Spring,註釋是否優於XML ?

基於註釋的配置的引入提出了一個問題,即這種方法是否比XML“更好”。簡而言之,答案是“視情況而定”。長篇大論的回答是,每種方法都有其優缺點,通常由開發人員決定哪種策略更適合他們。由於它們的定義方式,註釋在其聲明中提供了大量上下文,從而使配置更短、更簡潔。然而,XML擅長在不接觸源代碼或不重新編譯它們的情況下連接組件。一些開發人員喜歡將連接放在接近源的地方,而另一些人則認爲帶註釋的類不再是pojo,而且配置變得分散,更難控制。

無論選擇什麼,春天都可以同時容納兩種風格,甚至將它們混合在一起。值得指出的是,通過它的JavaConfig選項,Spring允許以一種非侵入性的方式使用註釋,而不涉及目標組件源代碼,並且在工具方面,Spring Tool Suite支持所有的配置風格。

XML設置的另一種替代方法是基於註釋的配置,它依賴於將組件連接起來的字節碼元數據,而不是尖括號聲明。開發人員不使用XML來描述bean連接,而是通過使用相關類、方法或字段聲明上的註釋將配置移動到組件類本身。如示例中所述:RequiredAnnotationBeanPostProcessor,將BeanPostProcessor與註釋結合使用是擴展Spring IoC容器的常見方法。例如,Spring 2.0引入了使用@Required註釋強制執行所需屬性的可能性。Spring 2.5使得采用相同的通用方法來驅動Spring的依賴項注入成爲可能。本質上,@Autowired註解提供了與Autowiring合作者描述的相同的功能,但是更細粒度的控制和更廣泛的適用性。Spring 2.5還增加了對JSR-250註釋的支持,比如@PostConstruct和@PreDestroy。Spring 3.0增加了對javax中包含的JSR-330 (Java依賴注入)註釋的支持。注入包,如@Inject和@Named。有關這些註釋的詳細信息可以在相關部分找到。

注意:註釋注入在XML注入之前執行。因此,XML配置覆蓋了通過這兩種方法連接的屬性的註釋。

與往常一樣,您可以將它們註冊爲單獨的bean定義,但是也可以通過在基於xml的Spring配置中包含以下標記來隱式註冊它們(請注意上下文名稱空間的包含):

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

(隱式註冊的後處理器包括AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor。)

注意:<context:annotation-config/>只在定義它的應用程序上下文中查找bean上的註釋。這意味着,如果你把<context:annotation-config/>放在一個DispatcherServlet的WebApplicationContext中,它只會檢查你的控制器中的@Autowired bean,而不會檢查你的服務。有關更多信息,請參見DispatcherServlet。

1.9.1 @Required

@Required註釋應用於bean屬性設置器方法,如下面的示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

該註釋指出,必須在配置時通過bean定義中的顯式屬性值或通過自動裝配填充受影響的bean屬性。如果未填充受影響的bean屬性,容器將拋出異常。這允許立即執行和顯式失敗,避免了以後出現NullPointerException實例或類似的情況。我們仍然建議將斷言放入bean類本身(例如,放入init方法)。即使在容器外部使用類,這樣做也會加強那些必需的引用和值。

注意:從Spring Framework 5.1開始,@Required註釋被正式棄用,支持爲所需的設置使用構造函數注入(或InitializingBean.afterPropertiesSet()的自定義實現以及bean屬性設置方法)。

1.9.2 使用@Autowired

注意:在本節的示例中,JSR 330的@Inject註釋可以代替Spring的@Autowired註釋。詳情請看這裏。

你可以將@Autowired註解應用到構造函數上,如下圖所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

注意:從Spring Framework 4.3開始,如果目標bean只定義一個構造函數,那麼就不再需要在這樣的構造函數上使用@Autowired註解。但是,如果有多個構造函數可用,那麼必須至少有一個用@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;
    }

    // ...
}

注意:確保您的目標組件(例如MovieCatalog或CustomerPreferenceDao)是由用於@ autowire註釋的注入點的類型一致聲明的。否則,注入可能會由於運行時“沒有找到類型匹配”錯誤而失敗。

對於通過類路徑掃描找到的xml定義的bean或組件類,容器通常預先知道具體的類型。但是,對於@Bean工廠方法,您需要確保聲明的返回類型具有足夠的表達能力。對於實現多個接口的組件或可能由其實現類型引用的組件,請考慮在工廠方法上聲明最特定的返回類型(至少與引用bean的注入點所需的返回類型一樣具體)。

您還可以指示Spring通過向需要該類型數組的字段或方法添加@Autowired註釋來從ApplicationContext中提供特定類型的所有bean,如下例所示:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

類型化集合也是如此,如下例所示:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}

注意:

如果希望數組或列表中的項按特定順序排序,您的目標bean可以實現org.springframework.core.Ordered接口或可以使用@Order或標準的@Priority註釋。否則,它們的順序將遵循容器中相應的目標bean定義的註冊順序。

您可以在目標類級別和@Bean方法上聲明@Order註釋,可能是針對單個bean定義(如果多個定義使用相同的bean類)。@Order值可能會影響注入點的優先級,但是要注意它們不會影響單例啓動順序,這是由依賴關係和@DependsOn聲明決定的正交關係。

注意,標準的jjavax.annotation.Priority在@Bean級別上不能使用Priority註釋,因爲它不能在方法上聲明。它的語義可以通過@Order值與針對每種類型的單個bean上的@Primary組合來建模。

即使是類型化的Map實例,只要期望的鍵類型是String,也可以自動生成。映射值包含預期類型的所有bean,鍵包含相應的bean名稱,如下例所示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

默認情況下,當給定注入點沒有匹配的候選bean可用時,自動裝配失敗。對於聲明的數組、集合或映射,至少需要一個匹配的元素。
默認行爲是將帶註釋的方法和字段視爲指示所需的依賴項。您可以改變這個行爲,如下面的例子所示,使框架能夠跳過一個不可滿足的注入點,通過標記它爲非必需的(例如,,通過設置@Autowired中的required屬性爲false):

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

如果一個非必需的方法的依賴項(或者它的一個依賴項,在有多個參數的情況下)不可用,那麼這個方法將不會被調用。在這種情況下,完全不會填充非必需字段,而是保留其默認值。

注入構造函數和工廠方法參數是一種特殊情況,因爲@Autowired中的required屬性有一些不同的含義,因爲Spring的構造函數解析算法可能會處理多個構造函數。默認情況下,構造函數和工廠方法參數是有效需要的,但是在單一構造函數場景中有一些特殊的規則,例如,如果沒有匹配的bean可用,則多元素注入點(數組、集合、映射)解析爲空實例。這就允許了一種通用的實現模式,在這種模式中,所有依賴項都可以在一個唯一的多參數構造函數中聲明——例如,在沒有@Autowired註解的情況下聲明爲一個公共構造函數。

注意:任何給定bean類只有一個構造函數可以聲明@Autowired,並將required屬性設置爲true,這表明構造函數在用作Spring bean時是自動裝配的。此外,如果required屬性被設置爲true,那麼只有一個構造函數可以被@Autowired註解。如果多個非必需的構造函數聲明註釋,它們將被視爲自動裝配的候選對象。將選擇通過匹配Spring容器中的bean來滿足最多依賴項的構造函數。如果沒有一個候選者可以滿足,那麼將使用主/默認構造函數(如果存在)。如果一個類一開始只聲明一個構造函數,那麼它總是會被使用,即使沒有註釋。帶註釋的構造函數不一定是公共的。

在setter方法上,建議使用@Autowired的required屬性,而不建議使用@Required註釋。將required屬性設置爲false表示該屬性對於自動裝配目的不是必需的,如果不能自動裝配,則忽略該屬性。另一方面,@Required更強大,因爲它強制通過容器支持的任何方式設置屬性,如果沒有定義值,則會引發相應的異常。

或者,您可以通過Java 8的Java .util來表示特定依賴項的非必需性質。可選,如下例所示:

public class SimpleMovieLister {

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

從Spring Framework 5.0開始,您還可以使用@Nullable註釋(任何包中的任何類型的註釋——例如,javax.annotation)。可空從JSR-305)或只是利用Kotlin內置的空安全支持:

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、@Value和@Resource註釋由Spring BeanPostProcessor實現處理。這意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor類型(如果有的話)中應用這些註釋。必須使用XML或Spring @Bean方法顯式地“連接”這些類型。

1.9.3 使用@Primary微調基於註釋的自動裝配

由於按類型自動裝配可能會產生多個候選對象,因此通常需要對選擇過程有更多的控制。實現此目的的一種方法是使用Spring的@Primary註釋。@Primary表示,當多個bean是要自動生成單值依賴項的候選bean時,應該優先考慮特定的bean。如果在候選bean中只存在一個主bean,那麼它就成爲autowired值。

考慮一下下面的配置,它將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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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 使用限定符微調基於註釋的自動裝配

@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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/>  //1

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

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> //2

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

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

</beans>

//1 主限定符值的bean與構造函數參數連接,構造函數參數使用相同的值進行限定。
//2 帶有action限定符值的bean與構造函數參數連接,構造函數參數使用相同的值進行限定。

對於回退匹配,bean名稱被認爲是默認的限定符值。因此,您可以使用main的id來定義bean,而不是使用嵌套的qualifier元素,從而得到相同的匹配結果。然而,儘管您可以使用這個約定來通過名稱引用特定的bean,但是@Autowired基本上是關於帶有可選語義限定符的類型驅動注入的。這意味着限定符值,即使使用了bean名稱回退,在類型匹配集內也總是具有收縮語義。他們沒有語義表達獨特的bean的引用id。良好的限定符值主要EMEA或持久,表達某個特定組件的特點是獨立於bean id,這可能是在匿名的情況下自動生成的bean定義等在前面的一個例子。

限定符也適用於類型化集合,如前所述—例如,設置<MovieCatalog>。在這種情況下,根據聲明的限定符,所有匹配的bean都作爲集合注入。這意味着限定符不必是唯一的。相反,它們構成了過濾標準。例如,您可以使用相同的限定符值“action”定義多個MovieCatalog bean,所有這些都被注入到一個使用@Qualifier(“action”)註釋的集合<MovieCatalog>。

在類型匹配的候選對象中,讓qualifier值對目標bean名進行選擇,不需要在注入點使用@Qualifier註釋。如果沒有其他解析指示器(例如限定詞或主標記),對於非唯一依賴情況,Spring將針對目標bean名稱匹配注入點名稱(即字段名或參數名),並選擇同名的候選項(如果有的話)。

也就是說,如果您打算通過名稱來表示註釋驅動的注入,那麼不要主要使用@Autowired,即使它能夠通過bean名稱在類型匹配的候選項中進行選擇。相反,使用JSR-250 @Resource註釋,它的語義定義是通過特定的名稱來標識特定的目標組件,聲明的類型與匹配過程無關。@Autowired具有相當不同的語義:在根據類型選擇候選bean之後,指定的字符串限定符值只在那些類型選擇的候選者中被考慮(例如,將一個帳戶限定符與標記有相同限定符標籤的bean相匹配)。

對於本身定義爲集合、映射或數組類型的bean, @Resource是一個很好的解決方案,它通過惟一名稱引用特定的集合或數組bean。也就是說,從4.3開始,收集,您可以通過Spring的@Autowired類型匹配算法來匹配映射和數組類型,只要元素類型信息保存在@Bean返回類型簽名或收集繼承層次結構中。在這種情況下,可以使用限定符值在相同類型的集合中進行選擇,如前一段所述。

在4.3中,@Autowired還考慮了注入的自引用(也就是說,回當前注入的bean的引用)。注意,自注入是一種退路。對其他組件的常規依賴始終具有優先級。從這個意義上說,自我推薦不參與常規的候選人選擇,因此尤其不屬於初選。相反,它們的優先級總是最低的。在實踐中,應該只將self引用作爲最後的手段(例如,通過bean的事務代理調用同一實例上的其他方法)。在這種情況下,考慮將受影響的方法分解到一個單獨的委託bean中。或者,您可以使用@Resource,它可以通過惟一的名稱將代理獲取到當前bean。

嘗試將來自@Bean方法的結果注入到相同的配置類中也是一種有效的自引用場景。要麼在方法簽名中實際需要的地方(與configuration類中的autowired字段相反)惰性地解析這些引用,要麼將受影響的@Bean方法聲明爲靜態的,將它們與包含它們的configuration類實例及其生命週期解耦。否則,只在回退階段考慮這些bean,而選擇其他配置類上的匹配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/>標記的子元素,然後指定類型和值來匹配您的自定義qualifier註釋。類型與註釋的完全限定類名匹配。另外,爲了方便起見,如果不存在名稱衝突的風險,可以使用簡短的類名。下面的例子演示了這兩種方法:

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

在[beans-classpath-scan]中,您可以看到一個基於註釋的替代方法,它可以在XML中提供限定符元數據。具體地說,看到[beans-scanning-qualifiers]。
在某些情況下,使用沒有值的註釋可能就足夠了。當註釋服務於更一般的用途,並且可以跨多個不同類型的依賴項應用時,這可能很有用。例如,您可以提供一個離線目錄,在沒有Internet連接時可以搜索該目錄。首先,定義簡單的註釋,如下例所示:

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

}

然後將註釋添加到要自動裝配的字段或屬性中,如下例所示:

public class MovieRecommender {

    @Autowired
    @Offline //1
    private MovieCatalog offlineCatalog;

    // ...
}

//1 這一行添加了@Offline註釋。
現在bean定義只需要一個限定符類型,如下面的例子所示:

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

//1 此元素指定限定符。
您還可以定義自定義限定符註釋,除簡單值屬性外,它還接受命名屬性。如果在要自動裝配的字段或參數上指定了多個屬性值,則bean定義必須匹配所有這些屬性值,才能被視爲自動裝配候選。以下面的註釋定義爲例:

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

    String genre();

    Format format();
}

在這種情況下,格式是enum,定義如下:

public enum Format {
    VHS, DVD, BLURAY
}

要自動生成的字段使用自定義限定符進行註釋,幷包含兩個屬性的值:類型和格式,如下面的示例所示:

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/>標籤中提供的值,如果沒有這樣的限定符的話,就像下面例子中的最後兩個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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

===使用泛型作爲自動裝配限定符
除了@Qualifier註釋之外,您還可以使用Java泛型類型作爲隱式的限定形式。例如,假設你有以下配置:

@Configuration
public class MyConfiguration {

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

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

假設前面的bean實現了一個泛型接口(即存儲<String>和存儲<Integer>),您可以@Autowire存儲接口並將泛型用作限定詞,如下面的示例所示:

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

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

通用限定符也適用於自動裝配列表、映射實例和數組。下面的例子自動裝配一個通用列表:

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

使用CustomAutowireConfigurer = = =
customautowirefigurer是一個BeanFactoryPostProcessor,它允許您註冊自己的自定義限定符註釋類型,即使它們沒有使用Spring的@Qualifier註釋。下面的例子展示瞭如何使用customautowirefigurer:

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

AutowireCandidateResolver通過以下方式確定autowire候選對象:

  • 每個bean定義的autowire-candidate值
  • <beans/>元素上可用的任何default-autowire-candidate模式
  • @Qualifier註解的存在,以及在customautowirefigurer中註冊的任何自定義註解

當多個bean符合自動裝配候選時,“主bean”的確定如下:如果候選bean中的一個bean定義的主屬性設置爲true,則選擇它。

===注入@Resource
Spring還通過在字段或bean屬性設置器方法上使用JSR-250 @Resource註釋(javax.annotation.Resource)來支持注入。這是Java EE中的一種常見模式:例如,在jsf管理的bean和JAX-WS端點中。Spring也支持Spring管理對象的這種模式。
@Resource接受name屬性。默認情況下,Spring將該值解釋爲要注入的bean名。換句話說,它遵循姓名語義,如下例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") //1
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

//1 這一行注入一個@Resource。
如果沒有顯式指定名稱,則默認名稱派生自字段名或setter方法。對於字段,它採用字段名。對於setter方法,它採用bean屬性名。下面的例子將把名爲movieFinder的bean注入到它的setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

注意:

註釋提供的名稱由CommonAnnotationBeanPostProcessor所感知的ApplicationContext解析爲bean名稱。如果顯式配置Spring的SimpleJndiBeanFactory,則可以通過JNDI解析這些名稱。但是,我們建議您依賴缺省行爲並使用Spring的JNDI查找功能來保持間接級別。

在沒有指定顯式名稱且類似於@Autowired的@Resource使用情況下,@Resource會找到一個主類型匹配項,而不是一個特定的已命名bean,並解決衆所周知的可解析依賴項:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource接口。

因此,在下面的示例中,customerPreferenceDao字段首先查找名爲“customerPreferenceDao”的bean,然後返回到customerPreferenceDao類型的主類型匹配:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; //1

    public MovieRecommender() {
    }

    // ...
}

//1上下文字段是基於已知的可解析依賴項類型:ApplicationContext注入的。
使用@ value = = =
@Value通常用於注入外化屬性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

配置如下:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

和以下應用程序。屬性文件:

catalog.name=MovieCatalog

在這種情況下,catalog參數和字段將等於MovieCatalog值。
Spring提供了一個默認的寬鬆嵌入式值解析器。它將嘗試解析屬性值,如果不能解析,屬性名(例如${catalog.name})將作爲值注入。如果你想嚴格控制不存在的值,你應該聲明一個PropertySourcesPlaceholderConfigurer bean,如下面的例子所示:

@Configuration
public class AppConfig {

     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
           return new PropertySourcesPlaceholderConfigurer();
     }
}

注意:使用JavaConfig配置PropertySourcesPlaceholderConfigurer時,@Bean方法必須是靜態的。

如果無法解析任何${}佔位符,則使用上述配置可確保Spring初始化失敗。也可以使用setPlaceholderPrefix、setPlaceholderSuffix或setValueSeparator等方法來定製佔位符。

注意:默認情況下,Spring引導配置一個PropertySourcesPlaceholderConfigurer bean,它將從application.properties獲取屬性和application.yml文件。

Spring提供的內置轉換器支持自動處理簡單的類型轉換(例如,轉換爲Integer或int)。多個逗號分隔的值可以自動轉換爲字符串數組,而不需要額外的工作。
可以提供一個默認值如下:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

Spring BeanPostProcessor在後臺使用一個ConversionService來處理將@Value中的字符串值轉換爲目標類型的過程。如果你想爲自己的自定義類型提供轉換支持,你可以提供自己的ConversionService bean實例,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

當@Value包含SpEL表達式時,該值將在運行時動態計算,如下例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL還支持使用更復雜的數據結構:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

===使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不僅識別@Resource註釋,還識別JSR-250生命週期註釋:javax.annotation.PostConstruct 和javax.annotation.PreDestroy。在Spring 2.5中引入了對這些註釋的支持,爲初始化回調和銷燬回調中描述的生命週期回調機制提供了另一種選擇。如果CommonAnnotationBeanPostProcessor是在Spring ApplicationContext中註冊的,那麼在生命週期的同一點上,就會調用攜帶這些註釋之一的方法,即對應的Spring生命週期接口方法或顯式聲明的回調方法。在下面的例子中,緩存在初始化時被預填充,在銷燬時被清除:

public class CachingMovieLister {

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

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

有關組合各種生命週期機制的效果的詳細信息,請參閱Combining Lifecycle Mechanisms

與@Resource一樣,@PostConstruct和@PreDestroy註釋類型也是JDK 6到8的標準Java庫的一部分。但是,整個javax。註釋包在JDK 9中與核心Java模塊分離,最終在JDK 11中被刪除。如果需要,可以使用javax。現在需要通過Maven Central獲得註釋-api工件,只需將其像其他庫一樣添加到應用程序的類路徑中即可。

==類路徑掃描和託管組件
本章的大多數示例使用XML指定配置元數據,這些元數據在Spring容器中生成每個bean定義。上一節(基於註釋的容器配置)演示瞭如何通過源代碼級註釋提供大量配置元數據。然而,即使在這些示例中,“基礎”bean定義也是在XML文件中顯式定義的,而註釋只驅動依賴項注入。本節描述通過掃描類路徑隱式檢測候選組件的選項。候選組件是根據篩選條件匹配的類,並且具有註冊到容器中的相應bean定義。這樣就不需要使用XML來執行bean註冊。相反,您可以使用註釋(例如,@Component)、AspectJ類型表達式或您自己的自定義篩選條件來選擇哪些類具有向容器註冊的bean定義。

從Spring 3.0開始,Spring JavaConfig項目提供的許多特性都是Spring核心框架的一部分。這允許您使用Java而不是使用傳統的XML文件定義bean。查看@Configuration、@Bean、@Import和@DependsOn註釋,瞭解如何使用這些新特性。

=== @Component和更多的原型註釋
@Repository註釋是任何滿足存儲庫角色或構造型(也稱爲數據訪問對象或DAO)的類的標記。該標記的用途之一是異常的自動翻譯,如異常翻譯中所述。

Spring提供了進一步的構造型註解:@Component, @Service和@Controller。@Component是任何spring管理組件的通用構造型。@Repository、@Service和@Controller是@Component對更具體用例(分別在持久性、服務和表示層)的專門化。因此,您可以使用@Component來註釋您的組件類,但是,通過使用@Repository、@Service或@Controller來註釋它們,您的類更適合通過工具進行處理或與方面相關聯。例如,這些原型註釋是切入點的理想目標。在Spring框架的未來版本中,@Repository、@Service和@Controller還可以附加語義。因此,如果您在使用@Component或@Service作爲服務層之間進行選擇,那麼@Service顯然是更好的選擇。類似地,如前所述,@Repository已經被支持作爲持久層中自動異常轉換的標記。

===使用元註釋和複合註釋
Spring提供的許多註釋可以在您自己的代碼中用作元註釋。元註釋是可以應用於其他註釋的註釋。例如,前面提到的@Service註釋是使用@Component進行元註釋的,如下例所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //1
public @interface Service {

    // ...
}

//1 組件導致@Service與@Component以相同的方式處理。
您還可以組合元註釋來創建“複合註釋”。例如,Spring MVC中的@RestController註釋由@Controller和@ResponseBody組成。

此外,複合註釋可以選擇性地從元註釋中重新聲明屬性,以支持自定義。當您只想公開元註釋屬性的一個子集時,這一點特別有用。例如,Spring的@SessionScope註釋將範圍名稱硬編碼到會話中,但仍然允許定製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;

}

你可以使用@SessionScope而不用聲明proxyMode如下:

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

你也可以覆蓋proxyMode的值,如下面的例子所示:

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

有關詳細信息,請參見Spring註釋編程模型wiki頁面 Spring Annotation Programming Model 。
===自動檢測類並註冊Bean定義
Spring可以自動檢測原型類,並在ApplicationContext中註冊相應的BeanDefinition實例。例如,以下兩個類可以進行自動檢測:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要自動檢測這些類並註冊相應的bean,需要將@ComponentScan添加到@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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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構建JAR時,請確保您沒有激活JAR任務的只文件開關。而且,在某些環境中,類路徑目錄可能不會根據安全策略公開—例如,JDK 1.7.0_45和更高版本上的獨立應用程序(這需要在清單中設置“可信庫”---查看 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)

在JDK 9的模塊路徑(Jigsaw)上,Spring的類路徑掃描通常按預期工作。但是,請確保在模塊信息描述符中導出了組件類。如果您希望Spring調用您的類的非公共成員,請確保它們是“打開的”(也就是說,它們在您的模塊-信息描述符中使用了一個打開聲明,而不是一個導出聲明)。

此外,在使用組件掃描元素時,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隱式包含的。這意味着這兩個組件是自動檢測和連接在一起的——所有這些都不需要XML中提供的任何bean配置元數據。

注意:您可以通過包含帶false值的annotation-config屬性來禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的註冊。

===使用過濾器自定義掃描
默認情況下,使用@Component、@Repository、@Service、@Controller、@Configuration註釋的類,或者本身使用@Component註釋的自定義註釋是唯一檢測到的候選組件。但是,您可以通過應用自定義過濾器來修改和擴展此行爲。將它們添加爲@ComponentScan註釋的includeFilters或excludeFilters屬性(或作爲XML配置中的<context:include-filter />或<context:exclude-filter /> <context:component-scan>元素的子元素)。每個篩選器元素都需要類型和表達式屬性。下表描述了過濾選項:

Table 5. Filter Types
Filter Type Example Expression Description

annotation (default)

org.example.SomeAnnotation

在目標組件的類型級別上呈現或元呈現的註釋。

assignable

org.example.SomeClass

目標組件可分配給(擴展或實現)的類(或接口)。

aspectj

org.example..*Service+

要由目標組件匹配的AspectJ類型表達式。

regex

org\.example\.Default.*

由目標組件的類名匹配的正則表達式。

custom

org.example.MyTypeFilter

自定義實現org.springframework.core.type.TypeFilter接口

下面的示例顯示了忽略所有@Repository註釋而使用“存根”存儲庫的配置:

@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或提供use-default-filters="false"作爲<component-scan/>元素的屬性來禁用默認過濾器。這有效地禁用了對帶有@Component、@Repository、@Service、@Controller、@RestController或@Configuration的類的自動檢測。

===在組件中定義Bean元數據
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組件,它的doWork()方法中有特定於應用程序的代碼。但是,它還提供了一個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);
    }
}

該示例將字符串方法參數country自動連接到另一個名爲privateInstance的bean上的age屬性值。Spring Expression Language元素通過符號#{< Expression >}定義屬性的值。對於@Value註釋,表達式解析器預先配置爲在解析表達式文本時查找bean名稱。

從Spring Framework 4.3開始,您還可以聲明類型爲InjectionPoint(或其更具體的子類:DependencyDescriptor)的工廠方法參數來訪問觸發當前bean創建的請求注入點。注意,這隻適用於bean實例的實際創建,而不適用於現有實例的注入。因此,該特性對於原型範圍內的bean最有意義。對於其他作用域,工廠方法只看到在給定作用域中觸發創建新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處理或應用其他約束。

您可以將@Bean方法聲明爲靜態方法,這樣就可以調用它們,而不需要創建包含它們的配置類作爲實例。這在定義後處理器bean(例如,類型爲BeanFactoryPostProcessor或BeanPostProcessor)時特別有意義,因爲這樣的bean在容器生命週期的早期初始化,並且應該避免在那時觸發配置的其他部分。

由於技術限制:CGLIB子類只能覆蓋非靜態方法,所以對靜態@Bean方法的調用不會被容器截獲,甚至在@Configuration類中也不會(如本節前面所述)。因此,直接調用另一個@Bean方法具有標準的Java語義,從而直接從工廠方法本身返回獨立的實例。

@Bean方法的Java語言可見性對Spring容器中的結果bean定義沒有直接影響。您可以自由地聲明您認爲適合於非@ configuration類的工廠方法,也可以在任何地方聲明靜態方法。然而,@Configuration類中的常規@Bean方法需要被覆蓋——也就是說,它們不能被聲明爲私有或final。

@Bean方法還可以在給定組件或配置類的基類上發現,也可以在組件或配置類實現的接口中聲明的Java 8缺省方法上發現。這爲組合複雜的配置安排提供了很大的靈活性,甚至可以通過Spring 4.2的Java 8默認方法實現多重繼承。

最後,一個類可能包含同一個bean的多個@Bean方法,這是根據運行時可用的依賴關係安排使用的多個工廠方法。這與在其他配置場景中選擇“最貪婪”的構造函數或工廠方法的算法相同:在構建時選擇具有最大數量可滿足依賴關係的變體,類似於容器在多個@Autowired構造函數之間進行選擇。

===命名自動檢測到的組件
當一個組件作爲掃描過程的一部分被自動檢測時,它的bean名稱由該掃描程序所知道的BeanNameGenerator策略生成。默認情況下,任何包含名稱值的Spring構造型註釋(@Component、@Repository、@Service和@Controller)都將該名稱提供給相應的bean定義。

如果這樣的註釋不包含名稱值或任何其他檢測到的組件(例如由自定義過濾器發現的組件),則默認bean名稱生成器將返回未大寫的非限定類名。例如,如果檢測到以下組件類,其名稱將是myMovieLister和movieFinderImpl:

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

作爲一般規則,當其他組件可能顯式引用它時,考慮使用註釋指定名稱。另一方面,只要容器負責連接,自動生成的名稱就足夠了。
===爲自動檢測的組件提供範圍

與通常的spring管理組件一樣,自動檢測組件的默認和最常見範圍是單例的。但是,有時您需要一個可以由@Scope註釋指定的不同範圍。您可以在註釋中提供作用域的名稱,如下面的示例所示:

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

注意:@Scope註釋僅在具體的bean類(用於帶註釋的組件)或工廠方法(用於@Bean方法)上內省。與XML bean定義相反,沒有bean定義繼承的概念,類級別的繼承層次結構與元數據目的無關。

有關Spring上下文中特定於web的作用域(如“請求”或“會話”)的詳細信息,請參閱請求、會話、應用程序和WebSocket作用域。與爲這些作用域預先構建的註釋一樣,您也可以通過使用Spring的元註釋方法來編寫自己的作用域註釋:例如,使用@Scope(“prototype”)進行自定義註釋元註釋,也可以聲明自定義作用域代理模式。

爲了提供範圍解析的自定義策略,而不是依賴於基於註釋的方法,您可以實現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中,將推理描述爲依賴項。爲此,在組件掃描元素上提供了作用域代理屬性。三個可能的值是:no、interface和targetClass。例如,標準JDK動態代理的配置如下:

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

===使用註釋提供限定符元數據
在使用限定符微調基於註釋的自動裝配中討論了@Qualifier註釋。本節中的示例演示瞭如何使用@Qualifier註釋和自定義qualifier註釋在解析autowire候選對象時提供細粒度控制。因爲這些示例是基於XML bean定義的,所以通過使用XML中bean元素的qualifier或meta子元素在候選bean定義上提供qualifier元數據。當依賴類路徑掃描來自動檢測組件時,您可以在候選類上提供帶有類型級註釋的限定符元數據。以下三個例子演示了這種技術:

@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在其限定符元數據中提供變體,因爲該元數據是按實例而不是按類提供的。

===生成候選組件的索引
雖然類路徑掃描非常快,但是可以通過在編譯時創建靜態候選列表來提高大型應用程序的啓動性能。在這種模式下,所有作爲組件掃描目標的模塊都必須使用這種機制。

注意:現有的@ComponentScan或<context:component-scan指令必須保持不變,以便請求上下文掃描某些包中的候選者。當ApplicationContext檢測到這樣的索引時,它會自動使用它,而不是掃描類路徑。

要生成索引,需要向每個模塊添加一個附加依賴項,其中包含的組件是組件掃描指令的目標。下面的示例展示瞭如何使用Maven實現這一點:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.2.2.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>

在Gradle 4.5及更早版本中,依賴項應該在compileOnly配置中聲明,如下例所示:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.2.2.RELEASE"
}

在Gradle 4.6及更高版本中,依賴項應該在annotationProcessor配置中聲明,如下例所示:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}

該過程生成一個包含在jar文件中的META-INF/spring.components文件。

注意:在IDE中使用這種模式時,必須將spring-context-indexer註冊爲註釋處理器,以確保在更新候選組件時索引是最新的。

警告:當在類路徑中找到一個META-INF/spring.components時,將自動啓用該索引。如果某個索引對於某些庫(或用例)是部分可用的,但是不能爲整個應用程序構建,那麼可以通過設置spring.index退回到常規的類路徑安排(就好像根本沒有索引一樣)。忽略true,不管是作爲系統屬性還是在spring中。類路徑根目錄下的屬性文件。

==使用JSR 330標準註釋
從Spring 3.0開始,Spring提供了對JSR-330標準註釋(依賴項注入)的支持。這些註釋的掃描方式與Spring註釋相同。要使用它們,您需要在類路徑中有相關的jar。

如果使用Maven,則使用javax。inject artifact可以在標準的Maven存儲庫中找到(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。您可以將以下依賴項添加到您的文件pom.xml:

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

===具有@Inject和@Named的依賴項注入
您可以使用@javax.inject來代替@Autowired。注入如下:

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。此外,您可以將注入點聲明爲提供者,從而允許按需訪問範圍較短的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一起使用。可選或@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) {
        // ...
    }
}

=== @Named和@ManagedBean:與@Component註釋等價的標準
您可以使用@javax.inject代替@Component。或javax.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的原型模型來構建定製的組件註釋。

=== JSR-330標準註釋的限制
當您使用標準註釋時,您應該知道一些重要的特性是不可用的,如下表所示:

Spring javax.inject.* javax.inject restrictions / comments

@Autowired

@Inject

@Inject沒有“required”屬性。可以與Java 8的Optional一起使用。

@Component

@Named / @ManagedBean

JSR-330不提供可組合模型,只提供了一種識別命名組件的方法。

@Scope("singleton")

@Singleton

JSR-330默認範圍類似於Spring的原型。但是,爲了使它與Spring的一般默認值保持一致,在Spring容器中聲明的JSR-330 bean在默認情況下是單例的。爲了使用單例之外的範圍,您應該使用Spring的@Scope註釋。javax。inject還提供了一個@Scope註釋。儘管如此,本教程僅用於創建您自己的註釋。

@Qualifier

@Qualifier / @Named

javax.inject.Qualifier is just a meta-annotation for building custom qualifiers. Concrete Stringqualifiers (like Spring’s @Qualifier with a value) can be associated through javax.inject.Named.

@Value

-

no equivalent

@Required

-

no equivalent

@Lazy

-

no equivalent

ObjectFactory

Provider

javax.inject.Provider是Spring的ObjectFactory的直接替代,只是使用了更短的get()方法名。它還可以與Spring的@Autowired結合使用,或者與非註釋的構造函數和setter方法結合使用。

==基於java的容器配置
本節介紹如何在Java代碼中使用註釋來配置:

===基本概念:@Bean和@Configuration
Spring新的java配置支持中的核心構件是@Configuration-annotated類和@Bean-annotated方法。

@Bean註釋用於指示方法實例化、配置和初始化要由Spring IoC容器管理的新對象。對於熟悉Spring的<beans/> XML配置的人來說,@Bean註釋的作用與<bean/>元素相同。您可以對任何Spring @Component使用@ bean註釋的方法。但是,它們最常與@Configuration bean一起使用。

使用@Configuration註釋類表明其主要目的是作爲bean定義的源。此外,@Configuration類允許通過調用同一類中的其他@Bean方法來定義bean之間的依賴關係。最簡單的@Configuration類如下:

@Configuration
public class AppConfig {

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

前面的AppConfig類等價於下面的Spring <beans/> XML:

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

完整的@Configuration和“精簡”的@Bean模式?
當@Bean方法在沒有使用@Configuration註釋的類中聲明時,它們被稱爲在“lite”模式中處理。在@Component或普通舊類中聲明的Bean方法被認爲是“精簡的”,包含類的主要用途不同,而@Bean方法在這裏是一種附加功能。例如,服務組件可以通過每個適用組件類上的附加@Bean方法向容器公開管理視圖。在這些場景中,@Bean方法是一種通用的工廠方法機制。

與完整的@Configuration不同,lite @Bean方法不能聲明bean之間的依賴關係。相反,它們對包含它們的組件的內部狀態以及可能聲明的參數(可選)進行操作。因此,這樣的@Bean方法不應該調用其他@Bean方法。每個這樣的方法實際上只是一個特定bean引用的工廠方法,沒有任何特殊的運行時語義。這裏的積極的副作用是不需要在運行時應用CGLIB子類,所以在類設計方面沒有限制(也就是說,包含的類可能是final,等等)。

在常見的場景中,@Bean方法是在@Configuration類中聲明的,以確保總是使用“full”模式,從而將交叉方法引用重定向到容器的生命週期管理。這可以防止通過常規Java調用意外調用相同的@Bean方法,這有助於減少在“lite”模式下操作時難以跟蹤的細微bug。

下面幾節將深入討論@Bean和@Configuration註釋。但是,我們首先介紹了通過基於java的配置創建spring容器的各種方法。

===使用註釋configapplicationcontext實例化Spring容器
下面幾節將介紹Spring 3.0中引入的註釋configapplicationcontext。這個通用的ApplicationContext實現不僅可以接受@Configuration類作爲輸入,還可以接受普通的@Component類和使用JSR-330元數據註釋的類。

當@Configuration類作爲輸入提供時,@Configuration類本身註冊爲bean定義,類中所有聲明的@Bean方法也註冊爲bean定義。

當提供@Component和JSR-330類時,它們被註冊爲bean定義,並假設DI元數據(如@Autowired或@Inject)在這些類中使用。

= = = =結構簡單
與在實例化ClassPathXmlApplicationContext時將Spring XML文件用作輸入非常相似,您可以在實例化註釋configapplicationcontext時將@Configuration類用作輸入。這使得Spring容器可以完全不使用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、Dependency1和Dependency2使用Spring依賴項注入註釋,比如@Autowired。
===使用register(Class<?>…)以編程方式構建容器

您可以使用一個無參數的構造函數來實例化一個帶註釋的configapplicationcontext,然後使用register()方法來配置它。當以編程方式構建一個帶註釋的configapplicationcontext時,這種方法特別有用。下面的例子演示瞭如何做到這一點:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

===啓用掃描組件(字符串…)
要啓用組件掃描,您可以將@Configuration類註釋如下:

@Configuration
@ComponentScan(basePackages = "com.acme") //1
public class AppConfig  {
    ...
}

//1 該註釋支持組件掃描。

注意:有經驗的Spring用戶可能熟悉與Spring上下文等價的XML聲明:namespace,如下例所示:

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

在前面的例子中,com。掃描acme包以查找任何@Component-annotated類,這些類在容器中註冊爲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方法都將作爲bean定義在容器中進行處理和註冊。

===支持帶有註釋configwebapplicationcontext的Web應用程序
註釋configapplicationcontext的WebApplicationContext變體可以與註釋configwebapplicationcontext一起使用。您可以在配置Spring ContextLoaderListener servlet偵聽器、Spring MVC DispatcherServlet等時使用這個實現。下面的web.xml片段配置了一個典型的Spring MVC web應用程序(注意上下文-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>

===使用@Bean註釋
@Bean是方法級別的註釋,是XML <bean/>元素的直接模擬。註釋支持<bean/>提供的一些屬性,比如:* init-method * destroy-method * autowiring * name。
您可以在@Configuration-annotated類或@Component-annotated類中使用@Bean註釋。
===聲明一個Bean

要聲明一個bean,您可以使用@Bean註釋來註釋一個方法。您可以使用此方法在作爲方法返回值指定的類型的ApplicationContext中註冊bean定義。默認情況下,bean名與方法名相同。下面的例子顯示了一個@Bean方法聲明:

@Configuration
public class AppConfig {

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

前面的配置完全等價於下面的Spring 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註釋的方法可以有任意數量的參數來描述構建該bean所需的依賴關係。例如,如果我們的TransferService需要一個AccountRepository,我們可以用一個方法參數來實現這個依賴,如下面的例子所示:

@Configuration
public class AppConfig {

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

解析機制與基於構造的依賴注入非常相似。有關詳細信息,請參閱相關部分。
===接收生命週期回調
使用@Bean註釋定義的任何類都支持常規的生命週期回調,並且可以使用JSR-250中的@PostConstruct和@PreDestroy註釋。請參閱JSR-250 annotations 瞭解更多細節。

也完全支持常規的Spring生命週期回調。如果一個bean實現了InitializingBean、DisposableBean或Lifecycle,容器將調用它們各自的方法。
標準的感知接口集(如BeanFactoryAware、BeanNameAware、MessageSourceAware、applicationcontext ware等)也得到了完全支持。
@Bean註釋支持指定任意初始化和銷燬回調方法,很像Spring XML在bean元素上的init-method和destroy-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將被自動登記到銷燬回調中。如果您有一個公共關閉或關閉方法,並且您不希望在容器關閉時調用它,那麼您可以向bean定義中添加@Bean(destroyMethod="")來禁用默認(推斷)模式。

在缺省情況下,您可能希望對使用JNDI獲得的資源這樣做,因爲它的生命週期是在應用程序之外管理的。特別要注意的是,一定要始終對一個數據源這樣做,因爲在Java EE應用服務器上這樣做是有問題的。
下面的例子展示瞭如何防止一個數據源的自動銷燬回調:

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

@ bean方法,您通常使用程序化的JNDI查找,通過使用Spring的JndiTemplate JndiLocatorDelegate助手或直接使用JNDI InitialContext但不是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作用域部分中指定的任何標準作用域。
默認的範圍是單例的,但是你可以用@Scope註釋來覆蓋它,如下面的例子所示:

@Configuration
public class MyConfiguration {

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

==== @Scope和scoped-proxy
Spring通過作用域代理提供了處理作用域依賴項的方便方法。使用XML配置時創建這樣一個代理的最簡單方法是<aop:scoped-proxy/>元素。用@Scope註釋在Java中配置bean可以提供與proxyMode屬性相同的支持。默認值是no proxy (ScopedProxyMode. no),但是您可以指定ScopedProxyMode。TARGET_CLASS或ScopedProxyMode.INTERFACES。

如果您使用Java將XML參考文檔中的作用域代理示例(參見作用域代理)移植到我們的@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賦予多個名稱,否則稱爲bean別名。@Bean註釋的name屬性爲此接受一個字符串數組。下面的例子展示瞭如何爲一個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();
    }
}

===使用@Configuration註釋
@Configuration是一個類級註釋,指示對象是bean定義的源。@Configuration類通過公共的@Bean註釋方法聲明bean。在@Configuration類上調用@Bean方法也可以用來定義bean之間的依賴關係。有關一般介紹,請參見[bean -java-基本概念]。

===注入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的引用。

注意:只有在@Configuration類中聲明@Bean方法時,這種聲明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();
    }
}

在clientService1()中調用一次clientDao(),在clientService2()中調用一次。因爲這個方法創建了一個新的ClientDaoImpl實例並返回它,所以您通常會希望有兩個實例(每個服務一個)。這肯定會有問題:在Spring中,實例化的bean默認有一個單例範圍。這就是神奇之處:所有@Configuration類都是在啓動時用CGLIB子類化的。在子類中,子方法在調用父方法並創建新實例之前,首先檢查容器是否有緩存的(作用域限定的)bean

注意:根據bean的範圍,行爲可能會有所不同。我們這裏說的是單例。

從Spring 3.2開始,不再需要將CGLIB添加到類路徑中,因爲CGLIB類已經在org.springframework下重新打包了。cglib並直接包含在spring-core JAR中。

由於CGLIB在啓動時動態添加特性,所以有一些限制。特別是,配置類不能是final。但是,從4.3開始,配置類上可以使用任何構造函數,包括使用@Autowired或單個非默認構造函數聲明進行默認注入。

如果希望避免cglib強加的限制,可以考慮在非@ @配置類上聲明@Bean方法(例如,在普通的@Component類上聲明)。然後不會攔截@Bean方法之間的交叉方法調用,因此必須完全依賴於構造函數或方法級別的依賴注入。

===組合基於java的配置
Spring的基於java的配置特性允許您編寫註釋,這可以減少配置的複雜性。
====使用@Import註釋
與在Spring 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.class和configB.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類。

注意:從Spring Framework 4.2開始,@Import也支持對常規組件類的引用,類似於註釋configapplicationcontext。註冊方法。如果您想避免組件掃描,這是特別有用的,通過使用一些配置類作爲入口點來顯式定義所有組件。

====注入導入的@Bean定義的依賴項
前面的例子有效,但過於簡單。在大多數實際場景中,bean在配置類之間相互依賴。在使用XML時,這不是問題,因爲不涉及編譯器,您可以聲明ref="someBean",並信任Spring在容器初始化期間解決它。當使用@Configuration類時,Java編譯器會對配置模型施加約束,因爲對其他bean的引用必須是有效的Java語法。

幸運的是,解決這個問題很簡單。正如我們已經討論過的,@Bean方法可以有任意數量的參數來描述bean依賴關係。考慮以下更真實的場景,其中有幾個@Configuration類,每個類都依賴於其他類中聲明的bean:

@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:這意味着它們可以利用@Autowired和@Value注入以及其他與其他bean相同的特性。

確保以這種方式注入的依賴項只是最簡單的類型。@Configuration類在上下文初始化過程中很早就被處理了,強制以這種方式注入依賴項可能會導致意外的早期初始化。儘可能使用基於參數的注入,如前面的示例所示。

另外,要特別注意通過@Bean定義的BeanPostProcessor和BeanFactoryPostProcessor。這些方法通常應該聲明爲靜態@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;

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

注意:只有在Spring Framework 4.3中才支持@Configuration類中的構造函數注入。還請注意,如果目標bean只定義一個構造函數,則不需要指定@Autowired。

Fully-qualifying imported beans for ease of navigation

在前面的場景中,使用@Autowired可以很好地工作,並提供了所需的模塊化,但是準確地確定在哪聲明瞭autowired bean的定義仍然是有些模棱兩可的。例如,作爲一名關注ServiceConfig的開發人員,您如何知道@Autowired AccountRepository bean的確切聲明位置?它在代碼中不是顯式的,這可能正好。請記住,Spring工具套件提供了一些工具,可以呈現顯示所有內容如何連接的圖表,這可能就是您所需要的全部內容。而且,您的Java IDE可以輕鬆地找到AccountRepository類型的所有聲明和使用,並快速地向您顯示返回該類型的@Bean方法的位置。

如果這種模糊性是不可接受的,並且您希望在IDE中從一個@Configuration類直接導航到另一個@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類及其依賴項與導航基於接口的代碼的通常過程沒有什麼不同。

注意:如果你想影響某些豆類的啓動創建訂單,考慮將其中一些聲明爲@Lazy(用於創建在第一次訪問,而不是在啓動時)或@DependsOn某些其他bean(確保特定的其他bean創建當前bean之前,超出後者的直接依賴關係暗示)。

====有條件地包含@Configuration類或@Bean方法
根據任意的系統狀態,有條件地啓用或禁用一個完整的@Configuration類,甚至單個的@Bean方法,這通常很有用。一個常見的例子是,只有在Spring環境中啓用了特定的配置文件時,才使用@Profile註釋來激活bean(有關詳細信息,請參閱[beans-definition-profiles])。

@Profile註釋實際上是通過使用一個靈活得多的名爲@Conditional的註釋實現的。@條件註釋表示特定的org.springframework.context.annotation。在註冊@Bean之前應該諮詢的條件實現。
條件接口的實現提供一個返回true或false的matches(…)方法。例如,下面的清單顯示了用於@Profile的實際條件實現:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // 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;
}

有關更多細節,請參見@Conditional javadoc。
===組合Java和XML配置

Spring的@Configuration類支持的目標不是100%完全替代Spring XML。一些工具(如Spring XML namespaces)仍然是配置容器的理想方法。在XML方便或必要的情況下,你有一個選擇:要麼在容器實例化在一個“以XML爲中心”的方式使用,例如,ClassPathXmlApplicationContext或實例化它“以java爲中心”的方式通過使用所和@ImportResource註釋導入XML。

以xml爲中心使用@Configuration類
最好是從XML引導Spring容器,並以特別的方式包含@Configuration類。例如,在使用Spring XML的大型現有代碼庫中,更容易根據需要創建@Configuration類,並從現有XML文件中包含它們。在本節的後面,我們將討論在這種“以xml爲中心”的情況下使用@Configuration類的選項。

將@Configuration類聲明爲普通Spring <bean/>元素
請記住,@Configuration類最終是容器中的bean定義。在本系列的示例中,我們創建了一個名爲AppConfig的@Configuration類,並將其作爲<bean/>定義包含在system-test-config.xml中。因爲打開了<context: annotated -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());
    }
}

下面的示例顯示了示例系統-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引用它,而且不太可能通過名稱顯式地從容器中獲取它。類似地,DataSource bean只根據類型進行自動生成,因此並不嚴格要求顯式bean id。

使用<context:component-scan/>獲取@Configuration類

因爲@Configuration使用@Component進行元註釋,所以帶有@Configuration註釋的類自動成爲組件掃描的候選類。使用與前面示例相同的場景,我們可以重新定義system-test-config.xml,以利用組件掃描。注意,在本例中,我們不需要顯式地聲明<context: annotated -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>

以配置類爲中心使用XML和@ImportResource
在@Configuration類是配置容器的主要機制的應用程序中,仍然可能需要至少使用一些XML。在這些場景中,您可以使用@ImportResource並只定義您需要的XML。這樣做實現了一種“以java爲中心”的方法來配置容器,並將XML保持在最低限度。下面的示例(包括一個配置類、一個定義bean的XML文件、一個屬性文件和主類)展示瞭如何使用@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
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);
    // ...
}

= =環境抽象
環境接口是集成在容器中的抽象,它對應用程序環境的兩個關鍵方面建模:概要文件和屬性。
配置文件是一組命名的邏輯bean定義,只有在給定的配置文件處於活動狀態時才向容器註冊。可以將bean分配給配置文件,不管它是用XML定義的還是用註釋定義的。與概要文件相關的環境對象的作用是確定哪些概要文件(如果有的話)當前是活動的,以及哪些概要文件(如果有的話)在默認情況下應該是活動的。

屬性在幾乎所有的應用程序中都扮演着重要的角色,它們可能來自各種各樣的來源:屬性文件、JVM系統屬性、系統環境變量、JNDI、servlet上下文參數、特別屬性對象、映射對象等等。與屬性相關的環境對象的作用是爲用戶提供一個方便的服務接口,用於配置屬性源並從中解析屬性。

=== Bean定義配置文件
Bean定義配置文件在覈心容器中提供了一種機制,允許在不同的環境中註冊不同的Bean。“環境”這個詞對不同的用戶可能意味着不同的東西,這個特性可以幫助許多用例,包括:

  • 在開發中處理內存中的數據源,而在QA或生產中從JNDI查找相同的數據源。
  • 僅在將應用程序部署到性能環境中時註冊監控基礎設施。
  • 爲客戶A和客戶B的部署註冊定製的bean實現。

考慮實際應用程序中需要數據源的第一個用例。在測試環境中,配置可能類似於以下內容:

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

現在考慮如何將此應用程序部署到QA或生產環境中,假設應用程序的數據源是在生產應用程序服務器的JNDI目錄中註冊的。我們的數據源bean現在看起來像下面的清單:

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

問題是如何根據當前環境在使用這兩種變體之間進行切換。隨着時間的推移,Spring用戶已經設計了許多方法來實現這一點,通常依賴於系統環境變量和包含${佔位符}標記的XML <import/>語句的組合,這些標記根據環境變量的值解析爲正確的配置文件路徑。Bean定義配置文件是一個核心容器特性,它爲這個問題提供了一個解決方案。

如果我們泛化前面的特定於環境的bean定義示例中顯示的用例,我們最終需要在特定上下文中註冊特定的bean定義,而不是在其他上下文中註冊。您可以這樣說,您希望在情形a中註冊bean定義的某個配置文件,在情形b中註冊另一個配置文件。

使用@Profile = = = =
當一個或多個指定的配置文件處於活動狀態時,@Profile註釋允許您指出一個組件有資格註冊。使用我們前面的例子,我們可以重寫數據源配置如下:

@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方法,如前所述,您通常選擇使用程序化的JNDI查找,通過使用Spring的JndiTemplate / JndiLocatorDelegate助手或直接使用JNDI InitialContext JndiObjectFactoryBean變體,但不是早些時候顯示這將迫使你聲明返回類型作爲FactoryBean類型。

配置文件字符串可以包含簡單的配置文件名稱(例如,產品)或配置文件表達式。概要文件表達式允許表達更復雜的概要文件邏輯(例如,production & us-east)。配置文件表達式支持以下操作符:

  • !:一個合乎邏輯的“不是”的配置文件
  • &:一個符合邏輯的“和”的配置文件
  • |:配置文件的邏輯“或”

注意:如果不使用括號,就不能混合使用&和|操作符。例如,production & us-east | eu-central不是有效的表達式。它必須表示爲生產& (us-east | eu-central)。

您可以使用@Profile作爲 meta-annotation,以創建自定義組合註釋。下面的例子定義了一個自定義的@Production註釋,你可以用它來代替@Profile(“production”):

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

注意:如果@Configuration類被標記爲@Profile,那麼與該類關聯的所有@Bean方法和@Import註釋都將被繞過,除非一個或多個指定的配置文件處於活動狀態。如果@Component或@Configuration類被標記爲@Profile({"p1", "p2"}),則除非配置文件'p1'或'p2'已被激活,否則不會註冊或處理該類。如果給定的配置文件以NOT操作符(!)爲前綴,則只有在配置文件不活動時才註冊帶註釋的元素。例如,給定@Profile({"p1", "!p2"}),如果配置文件'p1'是活動的,或者配置文件'p2'不是活動的,就會發生註冊。

@Profile也可以在方法級聲明,只包含配置類的一個特定bean(例如,一個特定bean的可選變體),如下例所示:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("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") 
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

=== XML Bean定義配置文件
XML對應項是<beans>元素的配置文件屬性。我們前面的示例配置可以在兩個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對等物不支持前面描述的概要表達式。但是,可以使用!操作符。也可以通過嵌套配置文件來應用邏輯“和”,如下面的例子所示:

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

在前面的示例中,如果生產和us-east配置文件都處於活動狀態,則公開數據源bean。

===激活Profile
現在我們已經更新了配置,我們仍然需要告訴Spring哪個配置文件是活動的。如果我們現在啓動我們的示例應用程序,我們將看到拋出一個NoSuchBeanDefinitionException,因爲容器無法找到名爲dataSource的Spring bean。

可以通過幾種方式激活配置文件,但最直接的方式是通過ApplicationContext提供的環境API以編程方式激活配置文件。下面的例子演示瞭如何做到這一點:

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

此外,您還可以通過spring.profiles聲明性地激活配置文件。活動屬性,可以通過系統環境變量、JVM系統屬性、web中的servlet上下文參數來指定。或者甚至作爲JNDI中的一個條目(參見 [beans-property-source-abstraction])。在集成測試中,可以通過使用spring-test模塊中的@ActiveProfiles註釋來聲明活動概要文件(參見context configuration with environment profiles)。

請注意,配置文件不是一個“非此即彼”的命題。您可以同時激活多個配置文件。通過編程,您可以向setActiveProfiles()方法提供多個配置文件名稱,該方法接受String…下面的例子激活多個配置文件:

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

聲明,spring.profiles.active可以接受逗號分隔的配置文件名稱列表,如下例所示:
-Dspring.profiles.active = " profile1 profile2”
= = = =默認的配置
默認配置文件表示默認啓用的配置文件。考慮下面的例子:

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

如果沒有激活配置文件,則創建數據源。您可以將此視爲爲一個或多個bean提供默認定義的一種方式。如果啓用了任何配置文件,則不應用默認配置文件。
您可以通過在環境中使用setDefaultProfiles()來更改默認配置文件的名稱,或者通過聲明的方式,通過使用spring.profiles.default屬性來更改名稱。

= = = PropertySource抽象
Spring的環境抽象在可配置的屬性源層次結構上提供搜索操作。考慮以下清單:

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屬性。爲了回答這個問題,環境對象對一組PropertySource對象執行搜索。PropertySource是對任何鍵值對源的簡單抽象,Spring的標準環境配置了兩個PropertySource對象——一個表示JVM系統屬性集(system . getproperties()),另一個表示系統環境變量集(system .getenv())。

使用@PropertySource = = =
@PropertySource註釋爲向Spring的環境添加PropertySource提供了一種方便的聲明性機制。
給定一個名爲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資源位置中出現的任何${…}佔位符都將根據已經在環境中註冊的屬性源集進行解析,如下面的示例所示:

假設我的。佔位符出現在已經註冊的一個屬性源中(例如,系統屬性或環境變量),佔位符被解析爲相應的值。如果沒有,則使用default/path作爲默認值。如果沒有指定默認值,並且無法解析屬性,則拋出IllegalArgumentException。

注意:根據Java 8的約定,@PropertySource註釋是可重複的。但是,所有這些@PropertySource註釋都需要在相同的級別上聲明,可以直接在configuration類上聲明,也可以作爲相同自定義註釋中的元註釋聲明。不建議混合使用直接註釋和元註釋,因爲直接註釋可以有效地覆蓋元註釋。

===語句中的佔位符解析
從歷史上看,元素中佔位符的值只能根據JVM系統屬性或環境變量來解析。現在情況已經不同了。因爲環境抽象是在整個容器中集成的,所以很容易通過它來路由佔位符的解析。這意味着您可以以任何您喜歡的方式配置解析過程。您可以更改通過系統屬性和環境變量進行搜索的優先級,或者完全刪除它們。您還可以適當地將自己的屬性源添加到組合中。
具體來說,下面的語句不管在哪裏定義客戶屬性,只要它在環境中可用:

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

==註冊LoadTimeWeaver
Spring使用LoadTimeWeaver在類加載到Java虛擬機(JVM)時動態地轉換它們。
要啓用加載時編織,可以將@ enableloadtime織造添加到@Configuration類之一,如下面的示例所示:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或者,對於XML配置,可以使用context:load-time-weaver元素:

<beans>
    <context:load-time-weaver/>
</beans>

一旦爲ApplicationContext配置好,該ApplicationContext中的任何bean都可以實現LoadTimeWeaverAware,從而接收對加載時編織器實例的引用。這在與 Spring’s JPA support相結合時特別有用,因爲在JPA類轉換中加載時編織可能是必需的。有關更多細節,請諮詢LocalContainerEntityManagerFactoryBean javadoc。有關AspectJ加載時編織的更多信息,請參見[aop- j-ltw]。

== ApplicationContext的附加功能
正如在介紹一章中所討論的,org.springframework.beans.factory包提供了管理和操作bean的基本功能,包括以編程的方式。org.springframework.context包添加了ApplicationContext接口,該接口擴展了BeanFactory接口,此外還擴展了其他接口,以更面向應用程序框架的風格提供附加功能。許多人以一種完全聲明式的方式使用ApplicationContext,甚至不以編程方式創建它,而是依賴於諸如ContextLoader之類的支持類來自動實例化ApplicationContext,作爲Java EE web應用程序正常啓動過程的一部分。

爲了以更面向框架的方式增強BeanFactory功能,上下文包還提供了以下功能:

  • 通過MessageSource接口訪問i18n風格的消息。
  • 通過ResourceLoader接口訪問資源,例如url和文件。
  • 事件發佈,即通過使用ApplicationEventPublisher接口實現ApplicationListener接口的bean。
  • 通過層次結構加載多個(層次結構)上下文,使每個上下文都集中於一個特定的層,例如應用程序的web層

===使用MessageSource進行國際化
ApplicationContext接口擴展了名爲MessageSource的接口,因此提供了國際化(“i18n”)功能。Spring還提供了層次化的messagesource接口,可以層次化地解析消息。這些接口一起提供了Spring影響消息解析的基礎。這些接口上定義的方法包括:

  • getMessage(String code, Object[] args, String default, Locale loc):用於從MessageSource檢索消息的基本方法。如果沒有找到指定區域設置的消息,則使用默認消息。使用標準庫提供的MessageFormat功能,傳入的任何參數都將成爲替換值。
  • getMessage(String code, Object[] args, Locale loc):與之前的方法基本相同,但有一點不同:不能指定默認消息。如果找不到消息,則拋出NoSuchMessageException。
  • getMessage(MessageSourceResolvable, Locale Locale):上述方法中使用的所有屬性也都包裝在一個名爲MessageSourceResolvable類中,您可以與此方法一起使用。

加載ApplicationContext時,它會自動搜索上下文中定義的MessageSource bean。bean必須具有messageSource名稱。如果找到這樣一個bean,對前面方法的所有調用都將委託給消息源。如果沒有找到消息源,ApplicationContext將嘗試查找包含同名bean的父進程。如果是,則使用該bean作爲消息源。如果ApplicationContext找不到任何消息源,則實例化一個空的委託messagesource,以便能夠接受對上述定義的方法的調用。

Spring提供了兩種MessageSource實現,ResourceBundleMessageSource和StaticMessageSource。兩者都實現了層次化的messagesource以實現嵌套消息傳遞。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>

下一個示例顯示了一個執行MessageSource功能的程序。請記住,所有ApplicationContext實現也是MessageSource實現,因此可以轉換爲MessageSource接口。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}

下一個示例顯示傳遞給消息查找的參數。這些參數被轉換成字符串對象並插入到查找消息中的佔位符中。

<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", Locale.ENGLISH);
        System.out.println(message);
    }
}

調用execute()方法的結果如下:

The userDao argument is required.

關於國際化(“i18n”),Spring的各種MessageSource實現遵循與標準JDK ResourceBundle相同的地區解析和回退規則。簡而言之,繼續前面定義的示例messageSource,如果您希望根據英國(en-GB)地區解析消息,您將創建名爲format_en_GB.propertiesexceptions_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的引用。在創建和配置bean時,在實現MessageSourceAware接口的ApplicationContext中定義的任何bean都將被注入應用程序上下文的MessageSource。

注意:作爲ResourceBundleMessageSource的替代,Spring提供了一個ReloadableResourceBundleMessageSource類。此變體支持相同的包文件格式,但比基於標準JDK的ResourceBundleMessageSource實現更靈活。特別是,它允許從任何Spring資源位置讀取文件(不僅僅是從類路徑),並支持熱重載bundle屬性文件(同時有效地在中間緩存它們)。有關詳細信息,請參見ReloadableResourceBundleMessageSource javadoc。

===標準和自定義事件
ApplicationContext中的事件處理是通過ApplicationEvent類和ApplicationListener接口提供的。如果將實現ApplicationListener接口的bean部署到上下文中,每當將ApplicationEvent發佈到ApplicationContext時,都會通知該bean。本質上,這是標準的觀察者設計模式。

注意:從Spring 4.2開始,事件基礎設施得到了顯著的改進,並提供了基於註釋的模型以及發佈任意事件的能力(即不需要從ApplicationEvent擴展的對象)。當這樣一個對象被髮布時,我們將它包裝成一個事件。

下表描述了Spring提供的標準事件:

Table 7. Built-in Events
Event Explanation

ContextRefreshedEvent

在初始化或刷新ApplicationContext時發佈(例如,通過使用ConfigurableApplicationContext接口上的refresh()方法)。在這裏,“初始化”意味着加載所有bean,檢測並激活後處理器bean,預實例化單例,以及ApplicationContext對象已準備好使用。只要上下文沒有關閉,就可以多次觸發刷新,前提是所選的ApplicationContext實際上支持這樣的“熱”刷新。例如,XmlWebApplicationContext支持熱刷新,但是GenericApplicationContext不支持。

ContextStartedEvent

通過在ConfigurableApplicationContext接口上使用start()方法啓動ApplicationContext時發佈。這裏,“啓動”意味着所有生命週期bean都接收一個顯式的啓動信號。通常,此信號用於在顯式停止之後重新啓動bean,但也可以用於啓動尚未配置爲自動啓動的組件(例如,尚未在初始化時啓動的組件)。

ContextStoppedEvent

通過在ConfigurableApplicationContext接口上使用stop()方法停止ApplicationContext時發佈。這裏,“停止”意味着所有生命週期bean都接收一個顯式的停止信號。可以通過start()調用重新啓動已停止的上下文。

ContextClosedEvent

通過在ConfigurableApplicationContext接口上使用close()方法或通過JVM關閉掛鉤關閉ApplicationContext時發佈。這裏,“關閉”意味着所有的單例bean都將被銷燬。一旦上下文關閉,它就會到達生命的終點,無法刷新或重新啓動。

RequestHandledEvent

一個特定於web的事件,通知所有bean HTTP請求已得到服務。此事件在請求完成後發佈。此事件僅適用於使用Spring的DispatcherServlet的web應用程序。

ServletRequestHandledEvent

RequestHandledEvent的子類,用於添加特定於servlet的上下文信息。

您還可以創建和發佈自己的自定義事件。下面的例子展示了一個簡單的類,它擴展了Spring的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容器檢測到EmailService實現了ApplicationEventPublisherAware並自動調用setApplicationEventPublisher()。實際上,傳入的參數是Spring容器本身。您正在通過其ApplicationEventPublisher接口與應用程序上下文進行交互。

要接收自定義ApplicationEvent,您可以創建一個實現ApplicationListener的類,並將其註冊爲一個Spring bean。下面的例子展示了這樣一個類:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

注意,ApplicationListener通常是用定製事件的類型參數化的(前一個示例中的BlackListEvent)。這意味着onApplicationEvent()方法可以保持類型安全,避免任何向下轉換的需要。您可以註冊任意數量的事件監聽器,但是請注意,默認情況下,事件監聽器同步接收事件。這意味着publishEvent()方法將阻塞,直到所有偵聽器都完成對事件的處理。這種同步和單線程方法的一個優點是,當偵聽器接收到事件時,如果事務上下文可用,它將在發佈者的事務上下文中進行操作。如果需要另一種事件發佈策略,請參閱javadoc以獲得Spring的ApplicationEventMulticaster接口和配置選項的SimpleApplicationEventMulticaster實現。

下面的例子展示了用於註冊和配置上述每個類的bean定義:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="[email protected]"/>
</bean>

總之,當調用emailService bean的sendEmail()方法時,如果有任何應該列入黑名單的電子郵件消息,則會發布BlackListEvent類型的自定義事件。blackListNotifier bean註冊爲ApplicationListener並接收BlackListEvent,此時它可以通知相關方。

注意:Spring的事件機制是爲同一應用程序上下文中Spring bean之間的簡單通信而設計的。然而,對於更復雜的企業集成需求,單獨維護的Spring integration項目提供了對構建輕量級、面向模式、事件驅動的體系結構的完整支持,這些體系結構構建於著名的Spring編程模型之上。

===基於註釋的事件監聽器
從Spring 4.2開始,您可以使用@EventListener註釋在託管bean的任何公共方法上註冊事件監聽器。黑名單通知可以重寫如下:

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() {
    // ...
}

還可以通過使用定義 SpEL expression 的註釋的條件屬性來添加額外的運行時篩選,該註釋屬性應該與實際調用特定事件的方法相匹配。
下面的例子展示瞭如何重寫我們的通知程序,只有在事件的內容屬性等於my-event時才能被調用:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每個SpEL表達式根據專用上下文計算。下表列出了對上下文可用的項,以便您可以將它們用於條件事件處理:

Name Location Description Example

Event

root object

The actual ApplicationEvent.

#root.event or event

Arguments array

root object

The arguments (as an object array) used to invoke the method.

#root.args or argsargs[0] to access the first argument, etc.

Argument name

evaluation context

The name of any of the method arguments. If, for some reason, the names are not available (for example, because there is no debug information in the compiled byte code), individual arguments are also available using the #a<#arg> syntax where <#arg> stands for the argument index (starting from 0).

#blEvent or #a0 (you can also use #p0 or #p<#arg> parameter notation as an alias)

= = = =異步監聽
如果希望特定的偵聽器異步處理事件,可以重用常規的@Async支持。下面的例子演示瞭如何做到這一點:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

在使用異步事件時要注意以下限制:

  • 如果異步事件偵聽器拋出異常,則不會將其傳播到調用者。有關更多細節,請參見AsyncUncaughtExceptionHandler。
  • 異步事件監聽器方法不能通過返回值來發布後續事件。如果您需要作爲處理的結果發佈另一個事件,請注入ApplicationEventPublisher來手動發佈事件。

= = = =Ordering Listeners
如果需要在調用另一個偵聽器之前調用一個偵聽器,可以在方法聲明中添加@Order註釋,如下面的示例所示:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

= = = =一般事件
還可以使用泛型進一步定義事件的結構。考慮使用EntityCreatedEvent<T>,其中T是創建的實際實體的類型。例如,您可以創建以下偵聽器定義來僅接收一個人的EntityCreatedEvent:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}

由於類型擦除,只有在觸發的事件解析事件偵聽器篩選的通用參數時纔有效(即,類似於類PersonCreatedEvent擴展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,也適用於作爲事件發送的任意對象。

===方便地訪問底層資源
爲了最佳地使用和理解應用程序上下文,您應該熟悉Spring的資源抽象,如 [resources]中所述。
應用程序上下文是ResourceLoader,可用於加載資源對象。資源本質上是JDK java.net.URL類的一個功能更豐富的版本。實際上,資源的實現在適當的地方封裝了java.net.URL的一個實例。資源可以以透明的方式從幾乎任何位置獲取底層資源,包括類路徑、文件系統位置、任何可以用標準URL描述的位置,以及其他一些變體。如果資源位置字符串是沒有任何特殊前綴的簡單路徑,那麼這些資源的來源是特定的,並且適合於實際的應用程序上下文類型。

您可以配置部署到應用程序上下文中的bean,以實現特殊的回調接口ResourceLoaderAware,以便在初始化時自動回調,並將應用程序上下文本身作爲ResourceLoader傳遞進來。您還可以公開Resource類型的屬性,用於訪問靜態資源。它們像其他屬性一樣被注入其中。您可以將這些資源屬性指定爲簡單的字符串路徑,並依賴於部署bean時從這些文本字符串到實際資源對象的自動轉換。

提供給ApplicationContext構造函數的位置路徑實際上是資源字符串,並且以簡單的形式根據特定的上下文實現進行適當的處理。例如,ClassPathXmlApplicationContext將簡單的位置路徑作爲classpath位置。您還可以使用帶有特殊前綴的位置路徑(資源字符串)來強制從類路徑或URL加載定義,而不管實際的上下文類型。

===方便的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(用於所有名稱以Context.xml結尾並駐留在WEB-INF目錄中的文件)和/WEB-INF/**/*Context.xml(對於WEB-INF的任何子目錄中的所有此類文件)

將Spring ApplicationContext部署爲Java EE RAR文件
可以將Spring ApplicationContext部署爲RAR文件,將上下文及其所需的所有bean類和庫jar封裝在Java EE RAR部署單元中。這相當於引導一個獨立的ApplicationContext(僅在Java EE環境中託管)來訪問Java EE服務器設施。RAR部署是部署無頭WAR文件的一種更自然的替代方案—實際上,沒有任何HTTP入口點的WAR文件僅用於在Java EE環境中引導Spring ApplicationContext。

RAR部署非常適合不需要HTTP入口點,而只由消息端點和計劃的作業組成的應用程序上下文。在這種上下文中,bean可以使用應用服務器資源,如JTA事務管理器和JNDI綁定的JDBC數據源實例和JMS ConnectionFactory實例,還可以向平臺的JMX服務器註冊——所有這些都是通過Spring的標準事務管理和JNDI和JMX支持設施實現的。應用程序組件還可以通過Spring的TaskExecutor抽象與應用服務器的JCA WorkManager交互。

有關RAR部署中涉及的配置細節,請參閱SpringContextResourceAdapter類的javadoc。
對於一個簡單的部署Spring ApplicationContext作爲一個Java EE RAR文件:

1.所有應用程序的類打包成一個RAR文件(這是一個標準的JAR文件與不同的文件擴展名)。閥門所有必需的庫JAR RAR存檔的根源。閥門一個meta - inf / ra.xml部署描述符(SpringContextResourceAdapter javadoc所示)和相應的Spring XML bean定義文件(s)(通常是meta - inf /中)。
2。將結果RAR文件放入應用服務器的部署目錄。

注意:這種RAR部署單元通常是自包含的。它們不向外界公開組件,甚至不向同一應用程序的其他模塊公開。與基於rar的ApplicationContext的交互通常通過與其他模塊共享的JMS目的地進行。例如,基於rar的ApplicationContext還可以調度一些作業或對文件系統中的新文件(或類似的東西)做出響應。如果它需要允許來自外部的同步訪問,它可以(例如)導出RMI端點,這些端點可能被同一機器上的其他應用程序模塊使用。

 

 

 

發佈了23 篇原創文章 · 獲贊 0 · 訪問量 480
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章