国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
InfoQ: Spring 2.0的新特性和應(yīng)用實踐


主題

Spring開源項目開始于2003年2月,現(xiàn)在框架正變得越來越強大。目前已經(jīng)達到了超過一百萬的下載量;在很多行業(yè)范圍內(nèi)成為事實上的標準;并且改變了企業(yè)Java應(yīng)用的開發(fā)過程。

最重要的是,它發(fā)展了大量而且忠誠的用戶,他們理解框架的關(guān)鍵價值并且共享反饋,來幫助框架高速發(fā)展。Spring的使命也一直很清晰:
  • 提供一種非入侵性編程模型。應(yīng)用程序的代碼應(yīng)該盡可能地與框架解耦。
  • 為內(nèi)部基礎(chǔ)設(shè)施提供一種優(yōu)秀的解決方案,以便開發(fā)者能將注意力放在交付業(yè)務(wù)價值,而不是解決普通的問題。
  • 使開發(fā)企業(yè)應(yīng)用程序盡可能簡單、增強,而不是減弱、機械。

在2006年10月份進入最終版的Spring 2.0,更加提升了這些價值。在核心團隊在2005年佛羅里達Spring體驗大會前研究發(fā)展特性時,我們發(fā)布了兩個關(guān)鍵的主題——簡易性和強大——突出作為Spring 2.0的主線,并且依舊忠實于Spring的使命。

某些決定很容易。從一開始,我們很清楚Spring2.0將會完全向后兼容,或者說盡可能地完全向后兼容。尤其考慮到Spring在很多企業(yè)中作為一個事實上的標準這樣一個定位,避免任何對用戶體驗的破壞是非常重要的。幸運地是,Spring一直致力于非入侵,這樣的目標就完全可以達到。

在十個月的Spring 2.0開發(fā)過程進行中,我們也需要考慮到一些Spring在2005到2006年的使用中越來越明顯的趨勢:

  • Spring越來越多地被一些非常大的組織來使用,從戰(zhàn)略的角度而不是只從項目角度來采用。這不僅意味著關(guān)于向后兼容的責任,而且是與大量不同類別的用戶相關(guān)的挑戰(zhàn)。
  • 越來越多數(shù)目的優(yōu)秀的第三方軟件產(chǎn)品正在內(nèi)部使用Spring,并需要容器的配置優(yōu)化和靈活性。這樣的例子很多,這里簡單列舉幾個:
    • 即將發(fā)布的BEA WebLogic Server 10,使用了Spring和Pitchfork項目來執(zhí)行注入和攔截。
    • BEA WebLogic Real Time(WLRT),來自于BEA的一種高端產(chǎn)品,致力于像前端辦公交易這樣的應(yīng)用,需要很低的等待時間。
    • 大量廣泛使用的開源產(chǎn)品,比如Mule、ServiceMix以及Apache JetSpeed門戶容器。
    • 一些企業(yè)廠商使用Spring集成他們自己的產(chǎn)品,比如GigaSpaces,Terracotta和Tangosol等。尤其是網(wǎng)格空間的公司,正在逐步投入Spring作為編程模型的選擇。
    • Oracle的SCA實現(xiàn),以及不同的其他Oracle產(chǎn)品。

因此我們需要確保當Spring變得對企業(yè)應(yīng)用開發(fā)者更加友好的同時,也要迎合這些苛刻的用戶。

從35000英尺

Spring 2.0的最大愿景是什么?

Spring 2.0提供了很大范圍內(nèi)的增強,其中最顯著的可能是:

  • 配置擴展:在Spring 2.0中,Spring支持可擴展的XML配置,使得使用自定義元素開發(fā)成為可能,它們?yōu)樯蒘pring bean的定義提供一種新層次的抽象。XML擴展機制同樣提供了一些新的標簽來簡化許多普通的任務(wù)。
  • 在AOP框架中有重要增強,使得既強大又更易于使用。
  • 增強對Java 5的支持。
  • 提供以動態(tài)語言實現(xiàn)Spring bean的能力,比如Groovy、JRuby和Beanshell,同時保留Spring組件模型的所有服務(wù),比如依賴注入,方便的聲明性服務(wù)以及AOP。
  • 以及許多新的特征,包括一個Portlet MVC框架,“消息驅(qū)動POJO”,與新的API的集成,包括JAVA持久化API(JPA),以及一個異步任務(wù)執(zhí)行框架。

有許多表面上不是很明顯的特征,但仍然很重要:

  • 對Ioc容器更進一步的擴展,使得在Spring之上構(gòu)建框架或產(chǎn)品更加容易。
  • 對Spring特有的集成測試支持的改善。
  • 提供AspectJ來暴露Spring核心功能給使用AspectJ和Spring的用戶,比如事務(wù)管理和依賴注入。

重要的是,這些特性被設(shè)計以和諧的整體來一起運行。

概述

這篇文章分為兩部分。在第一部分(就是本文),我們將會談到核心容器,XML配置擴展,AOP增強,以及特定于Java 5的特征。

在第二部分,我們將會談到消息,對動態(tài)語言的支持,Java持久化API以及Web層的增強。也會看一下表面以下的一些改進。

現(xiàn)在就讓我們更深入地研究一些新的特性,并且使用一些代碼范例來描述它們。

XML配置擴展

在Spring 2.0中最明顯的增強就是XML配置。

Spring Ioc容器實際上是獨立于元數(shù)據(jù)的表示的,比如XML。Spring以Java對象(BeanDefinition以及它的子接口)的形式擁有自己的內(nèi)部元數(shù)據(jù)。有一個對XML配置補充的研究,比如使用注解的Java配置

然而在今天,XML是被最多用在配置Spring上的,這就是Spring核心中配置改進的焦點。

Spring 2.0中XML的增強巧妙概括了簡易性和強大的主題:它們簡化執(zhí)行某些普通的任務(wù),但是也使得一些額外的高級任務(wù)成為可能。

目標

傳統(tǒng)上,在Spring的XML配置語法和Spring的內(nèi)部元數(shù)據(jù)之間有一對一的關(guān)系。一個元素產(chǎn)生一個BeanDefinition。

這通常就是我們想要的,并且對于配置那些框架并不了解的應(yīng)用程序類,也是理想的。

但是,如果框架應(yīng)該了解一個可能被反復(fù)使用的特定的類呢,比如象JndiObjectFactory這樣的普通的類,用來從JNDI尋找對象,并將其作為可注入的對象注入Spring容器,或者如果一些Bean定義只是在一起使用時才有意義呢?

這樣,一種新的抽象形式就能帶來重要的好處。

考慮一下下面這個例子,關(guān)于JNDI的lookup:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/jpetsore" />
</bean>

這當然要好過以前那些實現(xiàn)Service Locator的令人厭惡的日子,但它并不完美。我們會一直使用這個相同的類。并且(除非我們使用Java 5)沒有一種機制來指明“jndiName”屬性是必需的,而其他的屬性都是可選的。

Spring 2.0添加了一個方便的“jee”命名空間,其中包括允許我們執(zhí)行同樣的JNDI lookup的標簽,就像下面這樣:

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

這個比較簡單而且清楚。它清晰地表達了意圖而且更加簡潔。而且schema抓住了jndiName屬性是必需的這樣一個事實,并有方便的工具支持。其他可選的屬性同樣在這個schema中得以表達,就像下面這個例子:

<jee:jndi-lookup id="simple"
jndi-name="jdbc/MyDataSource"
cache="true"
resource-ref="true"
lookup-on-startup="false"
expected-type="com.myapp.DefaultFoo"
proxy-interface="com.myapp.Foo"/>

請注意在這樣一個相對簡單的示例中,attribute的名稱幾乎和被配置的類中property名稱一樣,這不是必需的。沒有必要做一個命名上的對應(yīng),或者在attribute和property之間做一對一的對應(yīng)。我們同樣能夠處理子元素內(nèi)容。而且,正如我早先提到的,我們可以從一個擴展標簽產(chǎn)生我們想要的任意數(shù)目的bean定義。

現(xiàn)在讓我們來考慮一個更加復(fù)雜的例子,其中我們要使用自定義標簽的能力來產(chǎn)生不止一個bean定義。

從Spring1.2開始,Spring就能夠識別@Transactional注解,并通過代理來使受影響的bean變成事務(wù)。這導(dǎo)致了一種簡單的部署模型——簡單地添加注解過的bean,它們就能自動變得可以處理事務(wù)——但是建立這樣模型需要一些令人心煩的戲法。為了所需的合作對象,需要三個bean定義——一個Spring AOPAdvisor,一個TransactionInterceptor,以及一個DefaultAdvisorAutoProxyCreator來引發(fā)自動的代理。這些bean定義組成了一個咒語,能夠在不同的應(yīng)用程序以不變的狀態(tài)使用,但是暴露了多過用戶不需要知道的細節(jié):

<bean
class="org.springframework...DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework...TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor
ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
class="org.springframework...TransactionInterceptor">
<property name="transactionManager"
ref="transactionManager"/>
<property name="transactionAttributeSource">
<bean
class="org.springframework...AnnotationsTransactionAttributeSource">
</bean>
</property>
</bean>

這是錯誤的抽象層次。你并不需要看到Spring AOP框架如何控制事務(wù)管理這種層次的細節(jié),本意不需要這樣的清晰。Spring 2.0在新的“tx”命名空間中提供了一個標簽,來替換所有這些定義,像下面這樣:

<tx:annotation-driven />

這個簡便的標簽達到了相同的效果。這個標簽清楚地表達了意圖——自動識別事務(wù)注解。那三個bean定義仍然通過底層的擴展標簽來創(chuàng)建,但那現(xiàn)在是框架的事情了,而不是用戶的。

Spring 2.0擴展標簽可以在必要時定義它自己的attribute以及子元素結(jié)構(gòu)。定義命名空間的開發(fā)者完全可控。處理Spring 2.0擴展標簽的NamespaceHandler可以從一個擴展標簽創(chuàng)建任意數(shù)目的bean定義。

為了使用擴展標簽,要使用XML schema而不是DTD,并且導(dǎo)入相關(guān)的命名空間。缺省的命名空間應(yīng)該是beans schema。下面的“tx”命名空間的例子,允許使用<tx:annotation-driven>:

<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
擴展標簽可以和正規(guī)的bean定義混合使用,可以引入任意數(shù)目的命名空間,并在同一個文檔中使用。

方便的命名空間

Spring 2.0提供一些方便的命名空間。其中最重要的是:

  • 事務(wù)管理(“tx”):在Spring 2.0中使Spring bean能夠處理事務(wù)變得相當容易,就像我們看到的那樣。它同樣使定義“transaction attributes”來映射事務(wù)行為到方法上變得簡單的多。
  • AOP(“aop”):Spring 2.0中專門的用來AOP配置的標簽比以前更加簡潔,Ioc容器不再需要依賴AOP框架。
  • Java EE(“jee”):這樣簡化了對JNDI和其他Java EE API的使用,正如我們看到的那樣。EJB lookup比JNDI lookup獲得的簡單性還要多。
  • 動態(tài)語言(“l(fā)ang”):以動態(tài)語言簡化bean的定義——Spring 2.0的一個新特性。
  • Utils(util):簡化加載java.util.Properties對象和其他通用的任務(wù)。

在Spring2.1以及往后版本中,將會加入更多的命名空間,來將簡單性引入新的領(lǐng)域。簡化Spring MVC和JPA使用的命名空間可能會最先加入到核心中去。

第三方配置擴展

作為一種擴展機制,Spring 2.0命名空間最重要的可能性是在Spring核心的外圍。

許多產(chǎn)品構(gòu)建于Spring基礎(chǔ)之上,它們的配置可以使用命名空間來變得更加簡單。一個很好的例子是Acegi Security forSpring(將在2007年早些時候改名為Spring Security),它需要配置一些協(xié)作bean的定義。Spring2.0的命名空間會使這變得非常簡單。再一次更加清楚地表達了簡單性的意圖。

許多產(chǎn)品和Spring緊密集成,這樣的好處不言而喻。Tangosol對Coherence的集成就是現(xiàn)成的案例。

其他潛在的例子包括支持Spring配置的產(chǎn)品,比如IBM的ObjectGrid。雖然ObjectGrid目前沒有在內(nèi)部使用Spring,但它被設(shè)計成通過Java來配置,使得能更加容易地集成到基于Spring的應(yīng)用程序中。擴展schema會讓這個變得相當簡單。

一個XML文檔使用某個擴展標簽作為頂層的元素是可能的。這樣避免需要通過命名空間給擴展schema元素加上前綴,意味著這樣的配置看起來更自然一些,而非以Spring為中心的。(通常,元素是在缺省的命名空間,因此傳統(tǒng)的Spring bean定義并不需要前綴。)

隨著時間過去,和JSP自定義標簽的發(fā)展,經(jīng)驗會通過實證明了的價值引出通用目的的標簽。我們期望用戶來創(chuàng)建命名空間的庫,來讓這個社區(qū)受益。

實現(xiàn)XML擴展

實現(xiàn)命名空間相對簡單。它分三步:

  1. 定義你的XML schema。這是最困難的一步,需要有合適的工具。對于schema沒有限制,當然你需要明白它是如何在運行時引導(dǎo)BeanDefinition的生成的。
  2. 實現(xiàn)NamespaceHandler接口,從你的schema中的元素和屬性來產(chǎn)生BeanDefinition。
  3. 編輯一個專門的注冊文件,spring.handlers,來讓Spring知道新建的NamespaceHandler類。

Spring配備的spring.handlers文件顯示了“標準”的命名空間是如何配置的:

http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

你可以在不同的/META-INF目錄擁有多個spring.handlers文件。Spring會在運行時合并它們。

一旦遵循了這些步驟,就可以使用你的新的擴展。

NameSpaceHandler接口并不難實現(xiàn)。它采用W3C DOM元素,并通過處理它們來生成BeanDefinition元數(shù)據(jù)。Spring解析XML:你的代碼僅僅需要遍歷XML樹。

public interface NamespaceHandler {
void init();
BeanDefinition parse(Element element, ParserContext parserContext);

BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}

parse()方法是最重要的,它負責將BeanDefinitions添加到提供的上下文中。

同樣要注意decorate()方法。NamespaceHandler同樣可以裝飾一個包含的bean定義,就像下面這個創(chuàng)建作用域內(nèi)的代理的語法所顯示的:

<bean id="scopedList" class="java.util.ArrayList" scope="request">
<aop:scoped-proxy/>
</bean>

<aop:scoped-proxy>標簽用來修飾包含它的標準的<bean>元素;如果它能夠訪問BeanDefinition,它就能對BeanDefinition進行修改。

為了簡化BeanDefinition元數(shù)據(jù)的生成,Spring2.0引入了一種方便的新的BeanDefinitionBuilder類,提供一種流暢的、構(gòu)建器風格的API。開始實現(xiàn)NamespaceHandlers的最佳指導(dǎo)是那些存在于Spring核心中的類。其中UtilNamespaceHandler是個相對簡單的例子;而AopNamespaceHandler是個比較高級的例子,它解析了一個復(fù)雜的子元素結(jié)構(gòu)。

最佳實踐:什么時候應(yīng)該定義你自己的命名空間?

你有錘子并不意味著其他一切都是釘子。正如我們已經(jīng)看到的,Spring2.0的XML擴展機制在很多案例中交付了很大的價值。然而,如果沒有很好的理由就不應(yīng)該使用它。因為XML擴展標簽是一種新的抽象,它同樣提供了一些需要學(xué)習的新的內(nèi)容。Spring的正規(guī)的XML格式對成千上萬的開發(fā)者來說已經(jīng)很熟悉了,甚至對那些新接觸Spring的人都是用直覺就可以判斷的。Spring XML文件提供了易于理解的某個應(yīng)用程序結(jié)構(gòu)的藍圖。如果過度配置使用了不熟悉的自定義標簽,就沒什么必要了。

讓我們在這個領(lǐng)域內(nèi)考慮一些相關(guān)的經(jīng)驗。JSP自定義標簽是個很好的例子。最終它們通過設(shè)計得很棒的標簽庫,比如JSTL,Struts和SpringMVC的標簽庫,產(chǎn)生了真實的價值。但在早些年,它們會引起厭惡,甚至是混亂的JSP頁面。(我在這里可以根據(jù)經(jīng)驗來解釋,因為我自己實現(xiàn)了一兩個這樣的標簽庫)。

把命名空間處理器看作是一個重要的新的擴展點,以及對Spring很有價值的新的抽象。它們對于那些在Spring之上構(gòu)建第三方產(chǎn)品的人來說非常棒;它們對于非常大型的項目也很有用。很快就會發(fā)現(xiàn),沒有了它們,很難想象生活會變成什么樣子。但是最終用戶還是應(yīng)該對實現(xiàn)它們持謹慎態(tài)度,但使用沒有問題。

當然,伴隨Spring提供的方便的擴展標簽,比如aop,tx以及jee命名空間,將很快成為Spring配置詞表的核心部分,就跟元素一樣被廣泛了解。你當然應(yīng)該優(yōu)先使用這些,而不是傳統(tǒng)的冗長的方式,來完成相同的任務(wù)。

語法糖

轉(zhuǎn)向使用schema也允許一點點快捷方式,比如對property值使用attribute而不是子元素。這些attribute不會被驗證,但因為我們使用的是XMLschema,而不是DTD,我們?nèi)匀豢梢员A羲衅渌尿炞C。因為attribute名稱就是property名稱,XML驗證不會再添加任何東西;這是基于Java的驗證的問題,而不是XML結(jié)構(gòu)的。

考慮一下下面這個Java對象,它有兩個簡單的property,以及對一個關(guān)聯(lián)對象的依賴:

public class Person {
private int age;
private String name;
private House house;
public void setAge(int age) {
this.age = age;
}

public void setName(String name) {
this.name = name;
}

public void setHouse(House house) {
this.house = house;
}

}

可以像下面這樣使用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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean class="com.interface21.spring2.ioc.Person"
p:name="Tony"
p:age="53"
p:house-ref="number10"
/>

<bean class="com.interface21.spring2.ioc.House"
id="number10"
p:name="10 Downing Street"
/>

</beans>

請注意property是如何通過使用attribute來提供的,而不是元素。這樣使用了特殊命名空間“p”的魔力。這個命名空間并沒有驗證,但允許使用attribute名稱來和Java property名稱進行匹配。

通過簡單的幾句,我們就簡化了使用“p”命名空間中的property名稱。就像“p:name”。當注入對其他Spring bean的引用時,使用“-ref”后綴,就像“p:house-ref”。

這種快捷的語法在你想要使用autowiring時尤其方便。比如,考慮下面的變量:

<bean class="com.interface21.spring2.ioc.Person"
autowire="byType"
p:name="Tony"
p:age="53"
/>

這里我們沒有設(shè)置“house”property,因為autowiring會考慮它。你甚至可以使用在<beans>元素層次使用default-autowire來在整個文件使用autowiring。

下面這個來自于Spring 1.0或者 1.1的用法演示了Spring配置在最近兩個主要的發(fā)布版本中(1.2和2.0)減少了多少數(shù)目的尖括號。

<bean class="com.interface21.spring2.ioc.Person">
<property name="name"><value>"Tony"</value></property>
<property name="age"><value>"53"</value></property>
<property name="house"><ref local="number10" /></property>
</bean>

在Spring1.2中,我們引入了“value”和“ref”attribute,在大多數(shù)情況下不需要子元素,而在Spring 2.0中則可以更純粹和簡單地使用attribute。

當然,傳統(tǒng)的XML格式可以繼續(xù)工作。當property值很復(fù)雜,而且不合法或者不能讀作一個attribute值時,就可以使用它們(傳統(tǒng)的XML格式)。而且,當然,沒有必要重寫已經(jīng)存在的配置文件。

除了XML配置擴展,在Spring Ioc容其中還有很多其他的新的特性。

其他Ioc容器增強

新的bean作用域

和XMl擴展一起,最重要的新的Ioc容器特性就是對于bean生命周期管理的新增的自定義作用域。

1.背景

Spring之前為bean提供了兩種作用域:單例原型(或者叫非單例)。

Singleton bean是一個在所屬容器上下文中的單例對象。在容器的生命周期中只會有一個實例存在,當容器關(guān)閉,它就會向所有需要知道容器關(guān)閉事件的單例bean發(fā)送事件通知——比如,關(guān)閉任何可以管理的資源,像連接池。

Prototypebean是任何時候通過注入到另外一個bean而被引用,或者是對所屬容器上getBean()調(diào)用的響應(yīng)時創(chuàng)建的。在這種情況下,bean定義與一個單個對象沒有關(guān)系,而是一個用來創(chuàng)建對象的配方。每個創(chuàng)建好的實例會有完全相同的配置,但會有不同的身份。Spring容器不會持有對原型的引用;它的生命周期由獲得它的那段代碼來負責。

在Spring 2.0中,我們添加了自定義作用域的能力??梢越o它們起任何名字。某個自定義作用域通常與能夠管理對象實例的后端存儲相對應(yīng)。在這種情況下,Spring提供了它熟悉的編程模型,支持注入和查找,而且后端存儲提供了作用域內(nèi)對象的實例管理。

典型的后端存儲有:

  • HTTP session
  • Clustered cache
  • 其他持久化存儲

2.Web作用域

對于這個特性,最通常的需求是關(guān)于在web應(yīng)用程序HTTP session中透明存儲對象。這在Spring 2.0中得到很方便的支持。因為這種需求很普通,所以舉例會很容易:

考慮下面這個bean的定義:

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

這里我們指定了“session”作用域而不是缺省的“singleton”。作用域可以指定成任何名字,但在Web應(yīng)用程序中提供“session”和“request”便于使用。

當我們通過名稱“userPreferences”調(diào)用getBean()時,Spring會很明顯地從當前HTTPSession中找出UserPreference對象。如果沒找到UserPreferences對象,Spring就會創(chuàng)建一個。注入使得可以為用戶描繪一個可以自定義的預(yù)配置的UserPreferences。

為了讓它工作起來,你需要在你的web.xml文件中像下面這樣定義一個過濾器。它會在底層處理一個ThreadLocal綁定,以便Spring能夠知道去哪個HTTP Session對象中去查找。

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

這樣就解決了查找的問題。但通常我們寧愿使用API盡量少的注入風格。如果我們想要把“userPreferences”bean注入到其他bean中去會發(fā)生什么呢,哪個會有更長的生命周期呢?比如,如果我們想在像下面這樣的一個單例SpringMVC控制器中采用單獨的UserPreferences對象時,又會發(fā)生什么呢:

public class UserController extends AbstractController {
private UserPreferences userPreferences;
public void setUserPreferences(UserPreferences userPreferences) { this.userPreferences = userPreferences;
}

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// do work with this.userPreferences
// Will relate to current user
}
}

在這個案例中,我們想要“非常及時”的注入,對短期存在的UserPreferences對象的引用是在被注入者使用的時候解析的。

通常有這樣的誤解,就是Spring注入是靜態(tài)的,因此必然是無狀態(tài)的。這并不正確。因為Spring有一個復(fù)雜的基于代理的AOP框架,它能在運行時通過提供這樣“非常及時”的功能來隱藏查找的動作。因為Ioc容器控制著注入的內(nèi)容,它能注入一個隱藏了需要查找的代理。

我們可以很容易地通知容器來執(zhí)行這樣的代理,像下面這樣。我們使用一個子元素來修飾userPreferences bean定義,

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

<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userController" class="com.mycompany.web.UserController">
<!-- a reference to the proxied ‘userPreferences‘ bean -->
<property name="userPreferences" ref="userPreferences"/>

</bean>
</beans>

現(xiàn)在解析會如期動態(tài)地發(fā)生;UserController中的userPreferences實例變量就會是從HTTPSession中解析出正確UserPreferences對象的代理。我們不需要直接跟HTTPSession對象打交道,而且可以輕松地對UserController進行單元測試,而不需要一個mock HttpSession對象。

另外一種獲得“非常及時”的注入的方式是使用一個查找方法。這是一個自從Spring1.1就存在的技術(shù),它使生命周期長的bean(通常是單例)能依賴一個潛在的生命周期短的bean。容器能夠重載一個抽象(或者具體)的方法來在方法被調(diào)用時返回執(zhí)行g(shù)etBean()調(diào)用的結(jié)果。

在這個例子中,我們沒有在被注入者中定義一個實例變量,而是定義了一個返回所需對象的抽象方法。方法必須是公有的或者受保護的:

public abstract class UserController extends AbstractController {
protected abstract UserPreferences getUserPreferences();

@Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// do work with object returned by getUserPreferences()
// Will relate to current user
}
}

在這個例子中,沒有必要使用一個作用域內(nèi)的代理。我們改變了被注入對象的bean定義,而不是被注入的bean。XML配置看起來應(yīng)該是這樣:

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

<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userController" class="com.mycompany.web.UserController">
<!-- a reference to the proxied ‘userPreferences‘ bean -->
<lookup-method name="getUserPreferences" bean="userPreferences" />

</bean>
</beans>

這種機制需要類路徑中包含CGLIB。

3.其他可能性

無疑,在真實的Spring風格中,底層的機制是可插拔的,而不是綁定到Web層的。比如,Tangosol Coherence作用域可以像下面這樣使用,通過Tangosol和Interface21提供的“datagrid”命名空間。

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

xmlns:datagrid="http://schemas.tangosol.com/schema/datagrid-for-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://schemas.tangosol.com/schema/datagrid-for-spring
http://schemas.tangosol.com/schema/datagrid-for-spring/datagrid-for-spring.xsd">

<datagrid:member/>

<bean id="brian" class="Person" scope="datagrid">
<aop:scoped-proxy/>

<property name="firstName" value="brian" />
<property name="lastName" value="oliver" />
<property name="age" value="21" />
</bean>
</beans>

在一個“datagrid”作用域內(nèi)聲明一個bean,意味著bean的狀態(tài)管理是由Coherence來執(zhí)行,在一個ClusteredCache中,而不是在本地的服務(wù)器上。當然,bean是由Spring來實例化和注入的,而且可以從Spring服務(wù)中受益。我們預(yù)見特定作用域內(nèi)的bean會在SOA和批處理的環(huán)境中非常有用,并且期望能在將來Spring的版本中添加更多方便的bean作用域。

4.自定義作用域

定義你自己的作用域很簡單。參考手冊詳細解釋了這個過程。你將需要一種策略來辨別如何在當前的作用域內(nèi)解析出對象。典型的情況下,這會涉及到ThreadLocal,就像Web作用域在底層所做的那樣。

類型推斷

如果你運行的是Java 5,Spring 2.0會從它新的能力中獲益,比如泛型。比如,考慮一下下面這個類:

public class DependsOnLists {
private List plainList;

private List<Float> floatList;

public List<Float> getFloatList() {

return floatList;
}

public void setFloatList(List<Float> floatList) {
this.floatList = floatList;
}
public List getPlainList() {
return plainList;
}

public void setPlainList(List plainList) {
this.plainList = plainList;
}

}

“plainList”屬性是個舊式風格的集合,而“flodatList”屬性有個類型化的說明。

考慮下面的Spring配置:

<bean class="com.interface21.spring2.ioc.DependsOnLists">
<property name="plainList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="floatList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
</bean>

Spring會正確地為“floatList”屬性填上浮點數(shù),而不是字符串,因為它能非常聰明地意識到它需要執(zhí)行類型轉(zhuǎn)換。

下面的測試演示了這種情況:

public class GenericListTest extends
AbstractDependencyInjectionSpringContextTests {

private DependsOnLists dependsOnLists;

public void setDependsOnLists(DependsOnLists dependsOnLists) {
this.dependsOnLists = dependsOnLists;
}

@Override
protected String[] getConfigLocations() {
return new String[] { "/com/interface21/spring2/ioc/inference.xml" };
}

public void testLists() {
List plainList = dependsOnLists.getPlainList();
List<Float> floatList = dependsOnLists.getFloatList();
for (Object o : plainList) {
assertTrue(o instanceof String);
System.out.println("Value=‘" + o + "‘, class=" +
o.getClass().getName());
}
for (Float f : floatList) {
System.out.println("Value=‘" + f + "‘, class=" +
f.getClass().getName());
}
}

}

輸出看起來會像下面這樣:

Value=‘1‘, class=java.lang.String
Value=‘2‘, class=java.lang.String
Value=‘3‘, class=java.lang.String
Value=‘1.0‘, class=java.lang.Float
Value=‘2.0‘, class=java.lang.Float
Value=‘3.0‘, class=java.lang.Float

新擴展點

在Ioc容器中有一些新的擴展點,包括:

  • 額外的PostPostProcessor鉤子,為像Pitchfork這樣的項目提供更強大的能力,來處理自定義注解,或者在Spring bean實例化和配置時執(zhí)行其他操作。
  • 添加任意元數(shù)據(jù)到BeanDefinition元數(shù)據(jù)的能力。添加信息很有用,雖然對Spring本身沒有意義,但可以被構(gòu)建于Spring之上的框架,或者像集群產(chǎn)品這樣跟Spring集成的產(chǎn)品來處理。

這些主題主要和高級用戶,以及那些使用Spring編寫產(chǎn)品的人有關(guān),這同樣超出本篇文章的范圍。但是理解Spring 2.0并不僅僅是在表面上增強,這很重要;它在底層完成了相當多的有難度的工作。

同樣也有大量的增強是支持與OSGi的集成,形成Spring OSGi集成項目,它將OSGi的動態(tài)模塊管理的能力與Spring組件模型相集成。這個工作會在Spring2.1中繼續(xù)進行,會把Spring的JAR文件打包成OSGi的bundle。

AOP增強

在Spring 2.0中最激動人心的增強之一是關(guān)于Spring AOP,它變得更加便于使用而且更加強大,主要是通過復(fù)雜而成熟的AspectJ語言的支持功能來實現(xiàn),而同時保留純的基于代理的Java運行時。

我們一直堅信AOP(面向切面編程)很重要。為什么?因為它提供給我們一種新的思考程序結(jié)構(gòu)的方法,能夠解決很多純OOP無法解決的問題——讓我們能夠在一個模塊中實現(xiàn)某些需求,而不是以發(fā)散的方式實現(xiàn)。

為了理解這些好處,讓我們考慮一些我們可以在需求中表達但無法直接用純OO代碼實現(xiàn)的情況。企業(yè)開發(fā)者使用一個通常的詞匯表來讓他們進行清楚的溝通。比如,像服務(wù)層,DAO層,Web層或者Web控制器這樣的術(shù)語,這不需要什么解釋。

許多需求是用這個詞匯表中的術(shù)語來表達的。比如:

  • 服務(wù)層應(yīng)該是可以處理事務(wù)的。
  • 當DAO操作失敗時,SQLException或者其他特殊持久化技術(shù)的異常應(yīng)該被翻譯,以確保DAO接口不會有漏掉的抽象。
  • 服務(wù)層對象不應(yīng)該調(diào)用Web層,因為各層應(yīng)該只依賴直接處在其下方的層。
  • 由于并發(fā)相關(guān)操作的失敗而導(dǎo)致失敗的等冪業(yè)務(wù)服務(wù)可以重試。

雖然這些需求都是現(xiàn)實存在的,并來自于經(jīng)驗,但它們并不能用純OOP來優(yōu)雅地解決。為什么?主要有兩個原因:

  • 這些來自于我們詞匯表的術(shù)語有意義,但它們并不是抽象。我們不能使用術(shù)語編程;我們需要抽象。
  • 所有這些都是所謂橫切關(guān)注點的例子。一個橫切關(guān)注點,在用傳統(tǒng)OO方法實現(xiàn)時,會分解成很多類和方法。比如,想象一下在跨DAO層遭遇特殊異常時要使用重試邏輯。這個關(guān)注點橫切許多DAO方法,而且在傳統(tǒng)的方式中會需要實現(xiàn)許多單獨的修改。

AOP就是通過對橫切關(guān)注點進行模塊化,并讓我們從普通的還可以編程的抽象的詞匯表來表達術(shù)語,來解決這樣問題的技術(shù),這些抽象叫做切入點,我很快會再解釋一些關(guān)于它們的細節(jié)。這種方法帶來一些主要好處,比如:

  • 因為減少了剪切粘貼風格的復(fù)制而減少代碼行數(shù)。這在像異常轉(zhuǎn)換和性能監(jiān)測這樣的try/catch/finally習慣用法中尤其有效。
  • 在單個代碼模塊中捕捉這樣需求的能力,提升可追蹤能力。
  • 在單個地方修補bug的能力,而不需要重新訪問應(yīng)用程序中許多位置。
  • 確保橫切關(guān)注點不混淆主要的業(yè)務(wù)邏輯——隨著開發(fā)的進展,這很有可能成為危險之處。
  • 開發(fā)者和團隊之間更好的職責分離。比如,重試功能可以有單個開發(fā)者或者團隊來編碼,而不需要由許多開發(fā)者跨多個子系統(tǒng)進行編碼。

因此AOP很重要,我們想提供最好的解決方案。

Spring AOP無疑是最廣泛使用的AOP技術(shù),歸功于以下優(yōu)點:

  • 采用成本幾近為零。
  • 提供正確的切入點,這才稱得上是AOP而不僅僅是攔截。
  • 提供一個支持許多使用方式的靈活的框架,可編程也可通過XML。

然而,在Spring 2.0之前,Spring中的AOP有一些缺點:

  • 不寫Java代碼,只能表達簡單的切入點。并沒有一種切入點表達語言來以字符串形式,簡潔表達復(fù)雜的切入點,雖然RegexpMethodPointcutAdvisor允許定義簡單正規(guī)的基于表達的切入點。
  • 當配置復(fù)雜AOP使用場景時,XML配置會變得很復(fù)雜。泛型元素被用來配置AOP類;雖然這對一致性來說很棒,對切面和類提供DI和其他服務(wù),但它沒有一個專門的配置方法來得簡潔。
  • Spring AOP不適合通知細粒度的對象——對象需要由Spring管理或者通過編程被代理。
  • 基于代理的方法的性能負載在少數(shù)案例中成為問題。
  • 因 為Spring AOP分離了代理和目標(被修飾或者被通知的對象),如果某個目標方法調(diào)用了目標上的方法,就不會使用到代理,意味著AOP通知并不適用。AOP使用基于 代理的方法的正反面影響超出了本文的范圍:有一些積極的因素(比如能夠?qū)ν粋€類的不同實例應(yīng)用不同的通知),但主要還是消極的。

為了在Spring 2.0中增強這個重要領(lǐng)域,我們希望在它的優(yōu)勢上構(gòu)建,同時解決缺點。

目標

最先的兩個缺點也是最顯著的。它們都跟切入點相關(guān)。后面的三個缺點在Spring用戶的正常使用中很少發(fā)生,如果它們證明是的確有問題的,我們建議使用AspectJ。(就像你會看到的,這是Spring AOP直接的進步。)

XML配置擴展解決了關(guān)鍵的挑戰(zhàn)之一。因為我們想要保持Spring模塊的設(shè)計,我們過去不能在SpringDTD中提供特定于AOP的標簽——因此在這種情況下需要依賴可以詳細一點的通用配置。隨著Spring 2.0的出現(xiàn),這樣的問題沒有了,因為XMLschema并不像DTD,它允許擴展。我們可以提供一個AOP命名空間,看起來能讓Ioc容器識別AOP結(jié)構(gòu),但不會影響模塊化。

AOP術(shù)語101:理解切入點和通知

讓我們簡要地修正一下某些AOP術(shù)語。如果你使用過AOP這些概念,可能對你來說很熟悉——這些概念是相同的,僅僅有一點不同,即更加優(yōu)雅和強大的表達方式。

切入點是匹配規(guī)則。它在程序執(zhí)行中確定應(yīng)該應(yīng)用某個切面的點的集合。這些點叫做連接點。在應(yīng)用程序運行時,連接點隨時會有,比如對象的實例化和方法的調(diào)用。在Spring AOP(所有版本)的案例中,唯一支持的連接點是公有方法的執(zhí)行。

通知是可以被切面應(yīng)用到連接點的行為。通知能在連接點之前或之后應(yīng)用。通知的所有類型包括:

  • Before advice:在連接點之前調(diào)用的通知。比如,記錄方法調(diào)用即將發(fā)生的日志。
  • After returning adive:如果在連接點的方法正常返回時調(diào)用的通知。
  • AfterThrowing advice(在Spring1.x中叫做Throws通知):如果連接點的方法拋出一個特殊的異常時調(diào)用的通知。
  • After advice:在連接點之后調(diào)用的通知,無論結(jié)果是什么。特別像Java中的finally。
  • Around advice:能夠完全控制是否執(zhí)行連接點的通知。比如,用來在事務(wù)中封裝某個方法調(diào)用,或者記錄方法的執(zhí)行時間。

切面是結(jié)合切入點和通知成一個模塊方案,解決特殊的橫切問題。

如果這有點抽象,請不要擔心:代碼示例會很快解釋清楚的。

對在Spring 2.0和AspectJ的環(huán)境中關(guān)于AOP基礎(chǔ)的更深討論,請參考Adrian在InfoQ上很棒的文章,"Simplifying Enterprise Applications with Spring 2.0 and AspectJ."

為什么會是AspectJ切入點表達式?

迄今為止,我們討論過的概念都是基本的AOP概念,對于Spring AOP或者AspectJ而且這并不特別,在Spring1.x中已經(jīng)是存在的。那么為什么我們選擇在Spring 2.0中采用AspectJ呢?

如果我們需要一種切入點表達語言,那么選擇就會很簡單。AspectJ有個思路很好,嚴格定義和充足文檔的切入點語言。它最近實現(xiàn)了一個當在Java 5上運行時,能對采用Java 5語法的編碼全面檢查。它不僅有很棒的參考材料,而且很多書籍和文章都對它進行了介紹。

我們不相信重新發(fā)明的輪子,而且定義我們自己的切入點表達語言是不合理的。進一步而言,自從AspectWerkz在2005年早期和冰島AspectJ項目之后,很明顯AspectJ是除了Spring 2.0之外唯一一個主流的AOP技術(shù)。因此關(guān)鍵的合并既是一種考慮也是一種技術(shù)優(yōu)勢。

新的XML語法

新的AOP命名空間允許Spring XML配置指定AspectJ切入點表達式,通過由切入點匹配的方法將通知指向任何Spring bean。

考慮一下我們上面看到的Person類。它有個age屬性,以及一個增加age的birthday方法:

public void birthday() {
++age;
}

我們假設(shè)有這樣的需求,任何時候調(diào)用birthday方法,我們都應(yīng)該發(fā)送一張生日賀卡給過生日的人。這是一個經(jīng)典的橫切需求:它并不是我們主要的業(yè)務(wù)邏輯部分,而是個單獨的關(guān)注點。理想的情況下,我們希望能夠?qū)⒛莻€功能模塊化,而不影響Person對象。

讓我們考慮一下通知。實際上,郵遞發(fā)送一張生日賀卡,或者甚至發(fā)送一張電子賀卡,當然會是這個方法的主要工作。然而,由于這篇文章中我們的興趣在于觸發(fā)的基礎(chǔ)結(jié)構(gòu),而不是發(fā)送生日賀卡的機制。因此我們就簡單使用控制臺輸出。這個方法需要訪問到過生日的這個Person,而且無論何時brithday方法被調(diào)用,它都應(yīng)該被調(diào)用。下面是簡單的通知實現(xiàn):

public class BirthdayCardSender {
public void onBirthday(Person person) {
System.out.println("I will send a birthday card to " +
person.getName() + "; he has just turned "
person.getAge());
}

}

本質(zhì)上我們在Person上想要一種Observer機制,但并不把Person修改成可被觀察的。請同時注意在這個例子中,BirthdayCardSender對象被用作一個切面,但不需要實現(xiàn)任何特定于框架的接口。這樣就使得使用已經(jīng)存在的類作為切面成為可能,而且擴展Spring的非入侵編程模型到潛在的切面以及普通的類。

通過Spring 2.0,我們可以像下面這樣把BirthdayCardSender當作一個切面來使用。首先,我們把BirthdayCardSender類定義成一個bean。這很簡單:

我們可以依賴注入這個對象,或者應(yīng)用任何其他的Spring組件模型服務(wù),如果我們愿意的話。

下一步,我們添加AOP配置來告訴Spring,無論何時Spring管理的Person對象的birthday()方法被調(diào)用了,就調(diào)用BirthdayCardSenderbean的onBirthday()方法。這是通過使用新的標簽來實現(xiàn)的。首先,我們必須導(dǎo)入方便的AOP命名空間:

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

接著我們使用新的AOP標簽,就像下面這樣。這是應(yīng)用這個切面的完整的配置:

標簽適用于after 通知。它指定方法調(diào)用onBirthday(),也就是通知。它指明了什么時候調(diào)用這個方法——切入點——以一個AspectJ切入點表達式的形式。

切入點表達式是關(guān)鍵。讓我們進一步來研究它。

execution(void com.interface21..Person.birthday()) and this(person)

execution()前綴表明我們正在匹配某個方法的執(zhí)行。也就是說,我們在改變方法的行為。

execution()一句的類型定義了匹配的方法。我們可以在這編寫一個表達式來匹配許多類的方法的集合。(實際上,那是一種更普遍更有價值的用法:當通知只匹配一個方法,實際上也沒什么意義)最后,我們把被調(diào)用的對象綁定到onBirthday()方法的參數(shù)。這樣縮小了切入點,可以僅僅匹配到一個Person對象的執(zhí)行——而且提供一種優(yōu)雅的方式來獲得被調(diào)用的對象,而不需要任何查找。我們可以使用參數(shù)綁定來綁定方法參數(shù),返回類型和異常,以及我們希望的目標。去掉查找的代碼應(yīng)該聽起來很熟悉:這是對被通知的類型的依賴有效的注入!在Spring的精神中,它去掉了一個API,無意間卻也使單元測試通知方法更簡單。

如果切入點表達式匹配一個或多個方法,任何定義在Spring上下文中的Person會被自動代理。那些沒有包含對于這個切入點的匹配的類的Bean不會受到影響,匹配切入點的沒有被Spring容器實例化的對象也一樣(不會受到影響)。這個切面配置是設(shè)計用來添加到上面Ioc實例中已有的bean配置,或者在一個額外的XML文件中,或者在同一個文件中。提示一下,Person定義成如下這樣:

<bean class="com.interface21.spring2.ioc.Person"
p:name="Tony"
p:age="53"
p:house-ref="number10"
/>

不需要任何配置來使Person或者其他的bean定義符合通知。當我們調(diào)用某個配置在Spring上下文中的Person對象的birthday()方法時,我們看到下面這樣的輸出:

I will send a birthday card to Tony; he has just turned 54

@AspectJ語法

通知總是包含在Java方法中。但到目前為止,我們看到的是定義在Spring XML中的切入點。

AspectJ 5也提供一種完美的解決方案,來定義切面,通知包含在方法中以及切入點在Java 5的注解中。這種切入點表達語言和AspectJ自己的語法一樣,而且語義相同。但以這種風格——叫做@AspectJ模型——切面能使用javac進行編譯。

通過@AspectJ語法,特定于框架的注解存在于切面中,而不是業(yè)務(wù)邏輯中。沒有必要把注解引入到業(yè)務(wù)邏輯中來驅(qū)動切面。

這種注解驅(qū)動的編程模型最先由AspectWerkz提出,在2005年早期它合并入AspectJ項目。在AspectJ中,@AspectJ 切面被加載時織入(load time weaving)運用:類加載器鉤子修改正在加載的類的字節(jié)碼,來應(yīng)用這些切面。AspectJ編譯器也明白這些切面,因此有個實現(xiàn)策略的選擇。

Spring 2.0為@AspectJ切面提供一種額外的選擇:Spring可以使用它的基于代理的AOP運行時來將這樣的切面應(yīng)用到Spring beans。

讓我們看一下,我們早先例子中同樣的功能是如何使用這種風格實現(xiàn)的。

這個切面類包含和BirthdayCardSender類相同的通知方法體,但使用org.aspectj.lang.annotation.Aspect注解來把它識別為一個切面。在同一個包中更多的注解定義了通知方法。

@Aspect
public class AnnotatedBirthdayCardSender {

@After("execution(void com.interface21..Person.birthday()) and this(person)")
public void onBirthday(Person person) {
System.out.println("I will send a birthday card to " +
person.getName() + "; he has just turned " +
person.getAge());
}
}

這將切入點和通知方法結(jié)合在一起,使切面成為一個完整的模塊。@AspectJ 切面,就像所有的AspectJ 切面,可以包含任意數(shù)目的切入點和通知方法。

在Spring XML中,我們再次把這個定義成一個bean,并添加一個額外的標簽來引起自動應(yīng)用切面。

<aop:aspectj-autoproxy />

<bean id="birthdayCardSenderAspect"
class="com.interface21.spring2.aop.AnnotatedBirthdayCardSender" />

這個自動代理aspectj標簽告訴Spring自動識別@AspectJ切面,并把它們應(yīng)用到任何與它們的切入點相匹配的相同上下文中bean中。

在XML和@AspectJ風格之間選擇

哪個方法更好呢——XML還是注解?首先,因為注解存在于切面中,而不是核心業(yè)務(wù)邏輯,所以轉(zhuǎn)換的成本并不高。決定通常取決于使切入點描述完全從Java代碼具體出來是否有意義。

如果你的切面是特定領(lǐng)域的,就考慮注解風格:也就是說,切入點和通知是緊密相聯(lián)的,而且通知并不普通,不可能在不同的場景中重復(fù)使用。比如說,發(fā)送生日賀卡對于注解風格是個很好的選擇,因為它是特定于一個特殊應(yīng)用類(Person)的。然而,一個性能監(jiān)測切面可能在不同的應(yīng)用中有不同的使用,通知方法最好從切入點解耦出來,切入點存在于XML配置中更自然一些。

使用XML如果:

  • 你不能使用Java 5,而且沒有其他選擇。Spring 2.0的AOP增強,除了能處理@AspectJ語法,也能工作在Java1.3,1.4以及Java 5上,雖然你不能編寫切入點表達式匹配注解或者其他的Java 5結(jié)構(gòu)。
  • 你可能想要在不同的上下文中使用通知。
  • 你想要使用已有代碼作為通知,而且不想引入AspectJ注解:比如,引入一個Observer行為調(diào)用任意POJO的方法。

編程用法

你也可以以編程的方式創(chuàng)建AOP代理,像下面這樣使用@AspectJ 切面:

Person tony = new Person();
tony.setName("Tony");
tony.setAge(53);

AspectJProxyFactory ajpf = new AspectJProxyFactory(tony);
ajpf.addAspect(new AnnotatedBirthdayCardSender());
Person proxy = ajpf.getProxy();

AnnotatedBirthdayCardSender會被自動識別為一個@AspectJ 切面,而且代理會使用它定義的行為來修飾目標。這種風格并不需要Spring Ioc容器。

編程式的代理創(chuàng)建在編寫基礎(chǔ)結(jié)構(gòu)代碼和測試時比較有用,但在通常的業(yè)務(wù)應(yīng)用中并不經(jīng)常使用。

通過AspectJ切入點你能做的很棒的事情

到目前為止我們看到的僅僅是表面的東西。

讓我們來看一些AspectJ切入點表達式更高級的能力,在Spring XML中,以@AspectJ風格(或者當然,AspectJ語言本身):

  • 參數(shù),目標,異常以及返回值綁定。
  • 從類型安全中受益,其中方法簽名中的類型是在切入點中指定。
  • 用切入點表達式的組合來構(gòu)建復(fù)雜的表達式。
  • 切入點表達式的重復(fù)使用。

我們已經(jīng)見過了目標綁定。讓我們舉一個參數(shù)綁定的例子:

@Aspect
public class ParameterCaptureAspect {

@Before("execution(* *.*(String, ..)) && args(s)")
public void logStringArg(String s) {
System.out.println("String arg was ‘" + s + "‘");
}
}

args()子句綁定到被匹配的方法中的參數(shù),縮小范圍到具有第一個參數(shù)類型是String的方法。因為切入點綁定了第一個參數(shù),必須是String類型的參數(shù),在通知方法中做強制轉(zhuǎn)換就沒有必要了。

這種機制很自然地提供了類型安全。這個切入點的通知目標永遠不可能被錯誤調(diào)用,通過錯誤類型的參數(shù),或者沒有匹配的參數(shù)。

為了給一個這種機制的優(yōu)越性的例子,下面是在Spring1.x MethodBefore通知中看起來的樣子。因為通過一個AOPAlliance MethodInterceptor(在Spring1.x中最通用的接口),我們需要遍歷一個參數(shù)數(shù)組來找出我們要尋找的參數(shù):

public class ParameterCaptureInterceptor implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
if (args.length >= 1 && method.getParameterTypes()[0] == String.class) {
String s = (String) args[0];
System.out.println("String arg was ‘" + s + "‘");
}
}
}

我們可以使用一個Spring AOP切入點來去除MethodBefore通知中的保護,但正如前面提到的,這樣可能需要編寫Java代碼。在攔截器中有個保護比使用切入點要慢,因為它不允許AOP運行時優(yōu)化出永遠不能調(diào)用的通知。

在這里我們能夠看到從攔截中去除AOP是多么的正確,以及為什么更簡單和更強大。EJB3.0攔截顯然比Spring的第一代AOP功能更加糟糕,因為它缺少一個真實的切入點機制,這意味著ClassCastException和ArrayIndexOutofBoundsException很可能是風險。同樣有必要使用Around通知(攔截器)而不是Before通知,因為EJB3.0沒有提供特殊的通知類型。而且,需要提供一個InvocationContext對象使得單元測試通知方法困難得多。

AspectJ切入點表達式語言的強大不僅僅是關(guān)于復(fù)雜的結(jié)構(gòu)。它也在避免潛在的錯誤和應(yīng)用程序更加智能化方面扮演重要的角色。它還能通過在通知方法中移除所需的保護代碼,顯著地減少所需代碼的數(shù)量。

組合和重用是真實語言的特征。AspectJ的主要目標就是把它們提供給切入點表達式。

讓我們看一下實際中切入點的重用。

@AspectJ語法(就像Spring 2.0的AOP XML格式)允許我們定義有名字的切入點。在@AspectJ語法,我們在一個void方法上使用@Pointcut注解,就像下面這樣:

@Pointcut("execution(public !void get*())")
public void getter() {}

@Pointcut注解允許我們定義切入點表達式,而且必要時,還有被切入點綁定的參數(shù)的個數(shù)和類型。方法名被用作切入點的名稱。

上面的切入點匹配JavaBean的getter方法。請注意這里匹配的優(yōu)勢:我們不僅僅處理通配符,而且處理語言語義。這方法會把getter方法看作任何名稱以get開頭的方法。這種切入點要健壯得多,因為它斷言getter是公有的,有一個非void的返回(!void)以及沒有參數(shù)(通過對參數(shù)的圓括號來指明)。

讓我們添加一種切入點來匹配返回int的方法:

@Pointcut("execution(public int *())")
public void methodReturningInt() {}

現(xiàn)在我們可以根據(jù)這些切入點來表達通知。我們第一個例子簡單引用了我們的第一個有名稱的切入點,“getter”:

@After("getter()")
public void getterCalled(JoinPoint jp) {
System.out.println("Method " + jp.getSignature().getName() +
" is a getter");
}

然而,現(xiàn)在事情變得更有趣了。我們通過把兩個切入點加到一個表達式,對一個返回in的getter應(yīng)用通知:

@After("getter() and methodReturningInt()")
public void getterCalledThatReturnsInt(JoinPoint jp) {
System.out.println("ANDing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter that also returns int");
}

ANDing意味著兩個切入點都必須應(yīng)用。ORing意味著必須應(yīng)用其中一個切入點。我們可以構(gòu)建我們需要的任何復(fù)雜程度的表達式。

在下面這個完整的切面中演示了ANDing和ORing:

@Aspect public class PointcutReuse {

@Pointcut("execution(public !void get*())" )
public void getter() {}

@Pointcut("execution(public int *())" )
public void methodReturningInt() {}

@Pointcut("execution(public void *(..))" )
public void voidMethod() {}

@Pointcut("execution(public * *())" )
public void takesNoArgs() {}

@After("methodReturningInt()" )
public void returnedInt(JoinPoint jp) {
System.out .println("Method " + jp.getSignature().getName() +
" returned int" );
}

@After("getter()")
public void getterCalled(JoinPoint jp) {
System.out .println("Method " + jp.getSignature().getName() +
" is a getter"
);
}

@After("getter() and methodReturningInt()" )
public void getterCalledThatReturnsInt(JoinPoint jp) {
System.out.println("ANDing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter that also returns int");
}

@After("getter() or voidMethod()" )
public void getterOrVoidMethodCalled(JoinPoint jp) {
System.out .println("ORing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter OR is void" );
}

}

這會產(chǎn)生下面的輸出,顯示切入點表達式的ORing和ANDing:

Method getName is a getter
ORing of pointcuts: Method getName is a getter OR is void
ORing of pointcuts: Method birthday is a getter OR is void
Method getName is a getter
ORing of pointcuts: Method getName is a getter OR is void
Method getAge returned int
Method getAge is a getter
ANDing of pointcuts: Method getAge is a getter that also returns int
ORing of pointcuts: Method getAge is a getter OR is void
I will send a birthday card to Tony; he has just turned 54

切入點組合同樣可以在Spring AOP XML中完成。在那種情況下,使用“and”和“or”替代“&&”和“||”操作符來避免使用XML屬性值的問題。

為高級用戶重用AspectJ庫切面

重用AspectJ語言編寫的AspectJ切入點表達式,并編譯成一個JAR文件是可能的。如果你使用Eclipse,你可以使用AJDT插件開發(fā)這樣的切面?;蛘?,如果你已經(jīng)在使用AspectJ,你就已經(jīng)擁有這樣的切面而且想要重用它們。

作為演示,我會重寫我們早先例子的一部分,把切入點放在一個AspectJ切面中:

public aspect LibraryAspect {

pointcut getter() :
execution(public !void get*());
...

}

這個切面用Aspect語言編寫,因此需要由AspectJ ajc編譯器來編譯。請注意我們可以使用aspectpointcut關(guān)鍵詞。

我們可以像下面這樣,在被用在Spring中的@AspectJ 切面中,引用這個切面。請注意我們使用了這個切面的FQN,它通常會被打包在某個位于類路徑的JAR文件中:

@Aspect
public class PointcutReuse {
@After("mycompany.mypackage.LibraryAspect.getter()")
public void getterCalled(JoinPoint jp) {
System.out.println("Method " + jp.getSignature().getName() +
" is a getter");
}

這個類,在另一方面,可以使用javac進行編譯并由Spring應(yīng)用。

你也可以在Spring XML中引用AspectJ 切面。正如你能看到的,Spring 2.0 AOP和AspectJ集成得非常緊密,雖然Spring AOP提供了一個完整的運行時,而不需要使用AspectJ編譯器或編織器。

如果你有非常復(fù)雜的切入點表達式,那使用AspectJ庫的切面是最好的實踐,因為那樣Aspect語言和工具支持是非常引人注目的。

最佳實踐

那么這對Spring用戶意味著什么?

很希望你同意AOP解決了企業(yè)軟件中很重要的問題,而且你意識到AspectJ編程模型相比較AOP或者任何可用于攔截的選擇,是多么的強大和優(yōu)雅。

你沒有必要把你已有的Spring MethodInterceptors或者其他的通知實現(xiàn)遷移到新的編程模型:它們?nèi)匀荒芄ぷ鞯暮芎?。但再往后,?yīng)該采用新的編程模型,它會更加引人注目。

如果你在使用ProxyFactoryBean或者TransactionProxyFactoryBean來一次一個地配置代理,你會發(fā)現(xiàn)自動代理(它在Spring 2.0中變得更容易更自然)能夠顯著減少Spring配置的工作量,以及在團隊成員間更好的分工。

運行時看起來怎樣?

雖然用法模型看起來不同,但記住概念很重要——連接點、切入點和通知——就跟Spring自2003年已經(jīng)實現(xiàn)的Spring AOP和AOP Alliance API中的一模一樣。Spring AOP對這些概念一直有對應(yīng)的接口。

也許更令人驚訝的是,表面以下的實現(xiàn)實際上更加相同。新的編程模型,支持AspectJ結(jié)構(gòu),構(gòu)建于已有的SpringAOP運行時之上。SpringAOP一直非常靈活,因此這不需要什么顯著的改變。(org.springframework.aop.framework.Advised接口仍然能夠用來查詢和修改AOP代理的狀態(tài),就像Spring1.x中那樣。)

這意味著你能夠使用AspectJ風格的切面,混合和匹配AOP Alliance和Spring AOP 切面:如果你想要支持已有的切面這尤其重要。

讓我們增加編程式創(chuàng)建代理的例子來演示這個。我們會增加一個傳統(tǒng)的Spring AOP風格的MethodInterceptor:

Person tony = new Person();
tony.setName("Tony");
tony.setAge(53);

AspectJProxyFactory ajpf = new AspectJProxyFactory(tony);
ajpf.addAspect(new AnnotatedBirthdayCardSender());
Person proxy = ajpf.getProxy();
ajpf.addAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("MethodInterceptor: Call to " + mi.getMethod());
return mi.proceed();
}
});

這會產(chǎn)生下面的輸出,來自于MethodInterceptor的輸出(沒有切入點,匹配所有的方法調(diào)用)和來自于@AspectJ風格編寫的BirthdayCardSender的輸出混雜在一起。

MethodInterceptor: Call to public void
com.interface21.spring2.ioc.Person.birthday()
MethodInterceptor: Call to public java.lang.String
com.interface21.spring2.ioc.Person.getName()
MethodInterceptor: Call to public int
com.interface21.spring2.ioc.Person.getAge()
I will send a birthday card to Tony; he has just turned 54

向著AOP統(tǒng)一

Spring2.0給AOP的世界帶來一種新的受歡迎的統(tǒng)一。第一次,切面的實現(xiàn)獨立于它的部署模型。我們看到的每一個@AspectJ例子都能使用AspectJ編譯器編譯,或者使用AspectJ加載時織入來使用,也能被Spring應(yīng)用。在Spring的精神中,我們有一個能夠跨越不同運行時場景的編程模型。

而且如果你想要采用AspectJ本身,這會有正常的進步,因為Spring把AspectJ切入點表達式概念帶給了更廣泛的受眾。

你什么時候應(yīng)該使用AspectJ呢?下面是一些指示:

  • 你想要通知細粒度的對象,它們可能不會被Spring容器實例化。
  • 除了公有方法的執(zhí)行,你想要通知連接點,比如字段訪問或者對象創(chuàng)建。
  • 你想要以透明的方式通知自調(diào)用。
  • 當一個對象需要被通知會被多次調(diào)用,而且不接受任何代理性能負載。(在這個基礎(chǔ)上做決定前要小心基準:Spring AOP代理的負載在通常使用中是無法覺察到的。)
  • 你想要使用AspectJ的能力來聲明由編譯器標記的警告或者錯誤。這對架構(gòu)增強尤其有用。

沒有必要有什么或者的選擇。同時使用AspectJ和Spring AOP是可能的:它們并不沖突。

Java 5

Spring 2.0保持向后對Java1.3和1.4的兼容。然而,Java 5帶來越來越多的新特性。

其中一些,比如已經(jīng)討論過的類型推斷,可以隨意使用。而其他的需要自己來選擇。讓我們快速預(yù)覽其中的一些。

新的API

大量的新的API在核心功能上提供Java 5的功能,這些核心功能繼續(xù)運行在Java的早些版本上。

尤其是:

  • SimpleJdbcTemplate:和熟悉的JdbcTemplate類似的新類,這使得JDBC使用更加簡單。
  • AspectJProxyFactory:和ProxyFactory類似的新類,設(shè)計用來使用@AspectJ 切面以編程方式創(chuàng)建代理。

隨著時間延續(xù),這樣的類的數(shù)目會越來越多。

SimpleJdbcTemplate是用來例證的。讓我們看一下在調(diào)用一個計算總數(shù)功能時的效果。在Java 5之前使用JdbcTemplate,我們需要在一個數(shù)組中封裝綁定參數(shù),就像下面這樣:

jdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
new Object[] { new Integer(13), "GBP" }
);

如果我們使用Java 5,自動裝箱去除了一點困擾,因為我們不再需要原始封裝器類型。這僅僅來自于語言特性,而不需要Spring提供任何新的東西:

jdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
new Object[] { 13, "GBP" }
);

然而,通過采用Java 5我們能夠完全不再需要對象數(shù)組。下面的例子顯示了SimpleJdbcTemplate是如何使用變參來綁定變量的,意味著開發(fā)者可以提供任意數(shù)目的變參,而不需要數(shù)組:

simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
13, "GBP"
);

作為增加的一個好處,我們不再需要區(qū)分帶有綁定和沒綁定參數(shù)的情況。雖然這需要JdbcTemplate上的兩個方法,來避免需要給執(zhí)行SQL的重載方法傳入一個空的Object數(shù)組,但通過SimpleJdbcTemplate,框架代碼可以檢查變參的長度。這樣下面的例子調(diào)用同一個方法:

simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT");

還有更顯著的好處。泛型使得簽名更加清晰,并去除強制轉(zhuǎn)換。比如,JdbcTemplate的queryForMap()方法返回一個ResultSet中的從列名到列值的Map。當它是SimpleJdbcTemplate上方法簽名的顯式部分時,這就變得清楚得多:

public Map<String, Object> queryForMap(String sql, Object... args)
throws DataAccessException

如果是返回這種map的列表的方法,那還會更清楚:

public List<Map<String, Object>> queryForList(String sql, Object ... args)
throws DataAccessException

增加SimpleJdbcTemplate的目標之一是,提供那些最經(jīng)常使用的方法。JdbcTemplate是Spring中最大的類之一,有很多的方法,其中一些是用于非常深奧的用途。在這種高級的情況下,根據(jù)全部問題的復(fù)雜性,語言語法糖就可能不再重要。對于這樣的案例,SimpleJdbcTemplate封裝了一個JdbcTemplate實例,通過getJdbcOperations()方法可以訪問到。

為了支持擴展DAO支持類的用法模型,Spring2.0提供了SimpleJdbcDaoSupport類,提供一個預(yù)先配置的JdbcTemplate。SimpleJdbcTemplate也象JdbcTemplate一樣很易于實例化或者直接注入,通過僅僅提供一個javax.sql.Datasource實現(xiàn)——這是Spring對所有關(guān)系型數(shù)據(jù)庫訪問的支持的開始點。就像JdbcTemplate,SimpleJdbcTemplate可以當作一個類庫來使用,而無需使用Spring的其他部分。

注解

我們首先在Spring1.2中引入注解,作為一個可選的特性,而且我們逐漸增加更多。

我已經(jīng)提到過來自于AspectJ的對AOP的注解的使用。它提供一種在單個代碼模塊中表達切入點和通知的優(yōu)雅的方式。

Spring還提供許多它自己的某些領(lǐng)域的注解,比如事務(wù)管理。下面這些在1.2中引入:

  • @Transactional:標記一個類,接口或者方法為可事務(wù)的。
  • 不同的注解,包括在org.springframework.jmx.export.annotation包中的@ManagedResource,識別操作、屬性和對象來到處以便JMX管理。

下面是2.0中最重要的新的注解:

  • @Configurable:表明某個特殊的對象應(yīng)該在構(gòu)建后使用Spring依賴注入,雖然它不是由Spring實例化的。驅(qū)動一個AspectJ DI切面,這在本文的第二部分中描述。
  • @Required: 指明所需的某個JavaBean 的setter方法。為了采用這個增強,在你的應(yīng)用上下文中定義RequiredAnnotationBeanPostProcessor。在 Spring的非入侵編程模型精神中,以及與已有代碼一起共同工作的能力,Spring也能加強其他注解的使用來指明某個所需的屬性,通過 RequiredAnnotationBeanPostProcessor的配置。
  • @Repository:把一個 DAO對象認作Repository模式(在領(lǐng)域驅(qū)動設(shè)計術(shù)語中)。Spring 2.0提供了一個切面(PersistenceExceptionTranslationAdvisor),能自動把來自于用@Repository注解 的對象的特定技術(shù)異常轉(zhuǎn)換成Spring的普通的DataAccessException。

對于JPA測試,Spring的集成測試創(chuàng)建了一些超類,比如新的AbstractJpaTest和泛型超類AbstractAnnotationAwareTransactionalTests,現(xiàn)在提供對于注解的支持,比如@Repeat(引起重復(fù)測試)和@ExceptedException(指明這個測試應(yīng)該拋出一個特殊的異常,如果沒有拋出就失敗)。不幸的是,由于JUnit3的設(shè)計基于具體的繼承,這些有用的注解對于使用Spring的其他測試不再有用。隨著JUnit4的廣泛使用,我們會提供我們集成測試的一個版本,它應(yīng)該能夠?qū)ζ渌挠脩糸_放這個功能。

如果你想要解釋自己的注解該怎么辦呢?當然在這種情況下,Spring的許多擴展鉤子會幫上忙。比如說,你能編寫一個BeanPostProcessor來使用給定的注解來區(qū)別方法,就跟RequiredAnnotationBeanPostProcessor一樣的工作方式。用于即將發(fā)布的WebLogic10的Pitchfork項目,使用這些擴展點在Spring之上實現(xiàn)了JSR-250注解和EJB3.0攔截注解。

同樣值得注意的是,Spring 2.0中提供的AspectJ切入點表達式語言有非常強大的注解匹配。很容易編寫切入點來匹配注解。比如,下面的切入點表達式會匹配任何用Spring框架中的注解進行注解的方法:

execution(@(org.springframework..*) * *(..))

下面的表達式會匹配任何用@Transaction注解的類:

@within(org.springframework.transaction.annotation.Transactional)

AspectJ 5是AspectJ語言的主要擴展,并且在保持隨著基礎(chǔ)語言的進化而更新方面給人印象深刻,切入點表達式也能夠匹配其他的Java 5結(jié)構(gòu),比如泛型和變參。


本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Spring高級程序設(shè)計 6 Spring AOP 進階
我用一篇文搞懂了《AOP面向切面編程》
【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我學(xué)spring3
Spring
spring aop術(shù)語解釋
【框架】124:spring框架之切面編程步驟說明
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服