Spring的AOP(Aspect Oriented Programming)框架允許你將分散在系統中的功能放到一個地方—切面。依賴Spring的強大切入點機制,何時何地在系統中采用切面你有很多種選擇。在本章中作者向我們介紹并且展示了Spring AOP基礎的方方面面。
一、AOP介紹
AOP提供了另外一種思考程序結構的角度,彌補了OOP(面向對象編程)的不足。大家可以參看夏昕編寫的《Spring開發指南》中的AOP部分,有簡單明晰的對比和解釋。就像剛開始理解OO概念一樣,對于新手來說AOP也是非常抽象難以理解的,不能僅從一個概念上去定義AOP。假如我們有一個系統,分為好多個模塊,每個模塊都負責處理一項重要的功能。但是每個模塊都需要一些相似的輔助功能如安全、日志輸出等等。這就是一種交叉業務,而這種“交叉”非常適合用AOP來解決。(在AOP剛出現的時候被大家翻譯成面向方面,而后來一部分人翻譯成面向切面。誰更正確?也許在真正理解了AOP以后才能判斷出來。)
1.AOP術語
l 切面(Aspect):切面是你要實現的交叉功能。它是應用系統模塊化的一個切面領域。
l 連接點(Join point):連接點是應用程序執行過程中插入切面的地點(在程序執行過程中某個特定的點)。在Spring AOP中一個連接點代表一個方法的執行。這個點可以是方法調用,異常拋出或者是要修改的字段。通過申明一個import org.aspectj.lang.JoinPoint類型的參數可以使通知(Advice)的主體部分獲得連接點信息。
l 通知(Advice):通知切面的實際實現(參考手冊:Action taken by an aspect at a particular join point.)。它通知應用系統新的行為。通知包括好多種類,在后面單獨列出。(Advice一詞在夏昕的《Spring開發指南》中被翻譯為“處理邏輯”)
l 切入點(Pointcut):切入點定義了通知應該應用在哪些連接點。通知(Advice)可以應用到AOP的任何連接點。通知(Advice)將和一個切入點表達式關聯,并在滿足這個連接點的切入點上運行(例如:在執行一個特定名稱的方法時)切入點表達式如何和連接點匹配是AOP的核心Spring使用缺省的AspectJ切入點的語法。
l 引入(Introduction):(也被叫做內部類型聲明“inter-type declaration”)引入允許你為已經存在的類添加新的方法和屬性。Spring允許引入新的接口(以及一個對應的實現)到任何被代理的對象。
l 目標對象(Target Object):目標對象是被通知對象。Spring AOP是運行時代里實現的,所以這個對象永遠是一個被代理對象。
l 織入(Weaving):把切面(Aspect)連接到其它的應用程序類型或者對象上來創建一個被通知(advised)的對象。可以在編譯時做這件事(例如使用AspectJ編譯器),也可以在類加載或運行時完成。 Spring和其他純Java AOP框架一樣, 在運行時完成織入。
通知(Advice)類型:
l 前置通知(Before Advice):在一個連接點之前執行的通知,但這個通知不能阻止連接點錢的執行(除非它拋出異常)
l 返回后通知(After returning advice):在一個連接點正常完成后執行的通知。例如:一個方法正常返回,沒有拋出任何異常。
l 拋出后通知(After throwing advice):在一個方法拋出異常時執行的通知。
l Finally后通知(After finally advice):當某連接點退出的時候執行的通知(不論是正常返回還是拋出異常)。
l 環繞通知(Around advice):包圍一個連接點的通知,就像方法調用。這是最強大的一種通知類型。環繞通知可以在方法調用前后完成自定義的行為。它也會選擇是否繼續執行連接點或直接返回他們自己的返回值域或拋出異常來結束執行。
2.Spring的AOP實現
在Spring中所有的通知都以Java類的形式編寫。所以你可以像普通Java開發那樣在IDE(Eclipse或NetBeans)中開發切面。而且,定義在什么地方應用通用的切入點通常在Spring的配置文件中配置。
Spring有兩種代理創建方式:
(1) 如果目標對象(Target Object)實現了一個或多個接口暴露的方法,Spring將使用JDK的java.lang.reflect.Proxy類創建代理。
(2) 如果目標對象
(Target Object)沒有實現任何接口,Spring使用CGLIB(http://cglib.sourceforge.net/ )庫生成目標對象的子類。在創建這個子類的時候,Spring將通知織入,并且將對目標對象的調用委托給這個子類。注意用這種方式創建代理時需要將Spring發行包中lib/cglib文件加下的jar文件加載到工程文件中。使用這種代理時注意兩點:
l 對接口創建代理優于對類創建代理,這樣會產生更加松耦合的系統。
l 標記為final的方法不能被通知。
通過上面的一大堆名詞解釋應該對AOP以及Spring中的AOP有了一個大概的了解了。下面進一步詳細解釋Spring中的AOP。
二、創建通知
1.前置通知(Before Advice)
創建前置通知需要實現org.springframework.aop.MethodBeforeAdvice接口,該接口只有before(Method method, Object[] args, Object target) throws Throwable這個方法。我們可以這樣理解前置通知:比如你到一個比較高檔的飯店去吃飯,進門時會有禮儀小姐給你開門并且向您問好,這個禮儀小姐的行為就可以視為“前置通知”。
package springinaction.chapter03.createadvice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
publicclass BeforeAdvice implements MethodBeforeAdvice
{
//實現MethodBeforeAdvice的before方法
publicvoid before(Method method, Object[] args, Object target)
{
System.out.println("befor advice");
}//end before
}//end class BeforeAdvice
2.返回后通知(After returning advice)
創建返回后通知需要實現org.springframework.aop.AfterReturningAdvice接口,該接口也只有一個方法:void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable。同樣我們也可以這樣理解After returning advice:當你在這家飯店用餐完后,禮儀小姐還會為您開門,并且歡迎您下次再來。這時禮儀小姐的行為就是“After returning advice”。
package springinaction.chapter03.createadvice;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
publicclass AfterReturningAdviceImp implements AfterReturningAdvice
{
publicvoid afterReturning(Object returnValue, Method method, Object[] arg2, Object target) throws Throwable
{
System.out.println("afterReturning");
}
}
3.環繞通知(Around advice)
創建環繞通知需要實現org.aopalliance.intercept.MethodInterceptor接口,同樣要實現一個invoke方法,該方法有一個MethodInvocation類型的參數。MethodInterceptor能夠控制目標方法是否真的被調用。通過調用MethodInterceptor.proceed()方法來調用目標方法。MethodInterceptor讓你可以控制返回的對象,你可以返回一個與proceed()方法返回對象完全不同的對象。我們可以這樣理解環繞通知:當你用餐完后,飯店要確保你已經付款了,在付款后停止再出售食物給你。
package springinaction.chapter03.createadvice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
publicclass AroundAdvice implements MethodInterceptor
{
public Object invoke(MethodInvocation mi)
{
Object obj = null;
//do something....
return obj;
}//end invoke(...)
}//end class AroundAdvice()
4.拋出后通知(After throwing advice)
創建拋出后通知需要實現org.springframework.aop.ThrowsAdvice接口,該接口沒有任何方法,但是要實現這個接口的類必須實現afterThorwing(Throwable throwable)或者afterThrowing(Method method, Object[] args, Object target)形式的一種,根據拋出的異常的類型恰當的方法將被調用。如果你在該飯店用餐后沒有付錢就走或者多給錢他們沒有找給你大概異常通知也就發生了。J
package springinaction.chapter03.createadvice;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
publicclass AfterThorwsAdvice implements ThrowsAdvice
{
publicvoid afterThorwing(Throwable throwable)
{
//do something....
}
publicvoid afterThrowing(Method method, Object[] args, Object target)
{
//do something....
}
}
一般來說環繞通知是用的最廣泛的一個通知類型,但是他們(Spring小組)鼓勵我們用最合適的通知。例如我們有一個簡單的驗證身份的功能,那么我們只需要前置通知就可能實現我們要求的功能了。
三、定義切入點
在上面我們定義了各種通知。可以看到一個通知是要被執行的一個方法。光把通知定義出來不行,我們還要確定這些通知在我們的系統什么地方應用,否則通知是毫無用處的。這就是切入點的用處。切入點決定了一個特定的類的特定方法是否滿足一條特定規則。如果確實符合,通知就應用到該方法上。
Spring根據需要織入通知的類和方法來定義切入點。Advice根據他們的特性織入目標類和方法。Spring的切入點框架的核心接口是Pointcut。
publicinterface Pointcut
{
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
ClassFilter接口決定了一個類是否符合通知的要求:
package org.springframework.aop;
publicinterface ClassFilter
{
boolean matches(Class clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
實現這個接口的類決定了以參數傳入進來的類是否應該被通知。ClassFilter.TRUE是規范的的適合任何類的ClassFilter實例,他適用于創建只根據方法決定時候符合要求的切入點。
ClassFilter接口利用類過濾切面,MethodMactcher接口可以通過方法過濾切面:
package org.springframework.aop;
import java.lang.reflect.Method;
publicinterface MethodMatcher
{
boolean matches(Method method, Class targetClass);
boolean isRuntime();
boolean matches(Method method, Class targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
MethodMactcher接口有三個方法。matches(Method, Class)根據目標類和方法決定一個方法是否該被通知。isRuntime()方法被調用來決定MethodMatcher的類型。有兩種類型:靜態類型和動態類型。靜態pointcut的意思是Advice總是被執行,此時的isRuntime()方法返回false。動態pointcut根據運行時方法的參數值決定通知是否需要執行,isRuntime()方法返回true。
有一個非常重要的名詞Advisor:大多數切面是由定義切面行為的通知和定義切面在什么地方執行的切入點組合而成的。在Spring中把通知和切入點組合到一個對象中。PointcutAdvisor提供這些功能。
package org.springframework.aop;
publicinterface PointcutAdvisor extends Advisor
{
Pointcut getPointcut();
}
Spring中的RegexpMethodPointcut讓你利用正則表達式來定義切入點。如果你對正則表達式不了解的話,趕快去學學吧,非常重要的內容。(開個玩笑的說,用了正則表達式能使你的代碼上檔次J),下面列出定義切入點時經常使用的符號:
符號
|
描述
|
示例
|
·
|
(英文點“.”)匹配任何單個字符
|
setFoo.匹配setFooB,但不匹配setFoo或setFooBar
|
+
|
匹配前一個字符一次或多次
|
setFoo.+匹配setFooB或setFooBar,但不匹配setFoo
|
*
|
匹配前一個字符0次或多次
|
setFoo.*匹配setFooB、setFooBar和setFoo
|
\
|
匹配任何正則表達式符號
|
\.setFoo.+匹配setFoo,但不匹配setFoo
|
下面引用《精通Srping》中的例子,這個例子講的還是比較清晰的。如果前面的Ioc一章理解的很好的話,理解這個例子不是很難。難點在于ProxyFactoryBean這個類。
package jingtongspring;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
publicclass LoggingBeforAdvice implements MethodBeforeAdvice
{
protectedstaticfinal Log log = LogFactory.getLog(LoggingBeforAdvice.class);
publicvoid before(Method arg0, Object[] arg1, Object arg2) throws Throwable
{
log.info("before: The invocation of getContent()");
}
}
下面是配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id = "helloworldbean"
class = "org.springframework.aop.framework.ProxyFactoryBean">
<property name = "proxyInterfaces">
<value>jingtongspring.IHelloWord</value>
</property>
<property name="target">
<ref local = "helloworldbeanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>loggingBerforeAdvisor</value>
</list>
</property>
</bean>
<bean id="helloworldbeanTarget"
class="jingtongspring.HelloWord"/>
<bean id="loggingBeforAdbisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="loggingBeforeAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean>
<bean id="loggingBeforeAdvice"
class="jingtongspring.LoggingBeforeAdvice"/>
</beans>
四、創建引入
在前面我們列出了Spring的四種通知類型,并且有相應的簡單例子。但是引入(Introduction)通知與其他類型的通知有所不同,它影響整個類。通過給需要消息的類添加方法和屬性來實現。(看上去很神氣,因為說道類那就是已經封裝好了的,給類添加方法除非是繼承該類。Spring中怎樣實現?)
Spring通過一個特殊的方法攔截器接口IntroductionInterceptor來實現引入。這個接口添加一個方法:boolean implementsInterface(Class intf)這個方法對于引入如何工作非常關鍵。如果IntroductionInterceptor是為了實現指定接口,那么方法implementsInterface應該返回true。也就是說,對用這個接口聲明的方法的任何調用將被委托給IntroductionInterceptor的invoke()方法。invoke()方法負責實現這個方法,不能調用MethodInvocation.proceed()。它引入了新的借口,調用目標對象是沒有用的。(注意:在本書中提到了一個IntroductionMethodInterceptor接口,我查看Spring2.0 rc3的源代碼并沒有該類,可能是在Spring1.x中存在的,IntroductionInterceptor 接口的定義形式是這樣的:publicinterface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {},invoke()方法估計是在它的兩個父接口之一中存在。)
下面看例子:
先實現一個接口:
package springinaction.chapter03.createadvice;
import java.util.Date;
publicinterface Auditable
{
void setLastModifiedDate(Date date);
Date getLastModifiedDate();
}
再實現一個類:
package springinaction.chapter03.createadvice;
import java.util.Date;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.IntroductionInterceptor;
publicclass AuditableMixin implements IntroductionInterceptor, Auditable
{
private Date lastModifiedDate;
publicboolean implementsInterface(Class intf)
{
return intf.isAssignableFrom(Auditable.class);
}//end implementsInterface(...)
public Object invoke(MethodInvocation m) throws Throwable
{
if(this.implementsInterface(m.getMethod().getDeclaringClass())){
return m.getMethod().invoke(this, m.getArguments());
}
else{
return m.proceed();
}
}//end invoke(...)
public Date getLastModifiedDate()
{
returnthis.lastModifiedDate;
}//end getLastModifiedDate()
publicvoid setLastModifiedDate(Date lastModifiedDate)
{
this.lastModifiedDate = lastModifiedDate;
}
}//end class AuditableMixin
在AuditableMixin類中實現了IntroductionInterceptor接口和業務接口Auditable。如果被申明調用方法的類型是Auditable類型,implementsInterface方法返回true。這說明,對于Auditable的兩個方法,攔截器必須提供實現,在invoke方法中實現。對于任何Auditable接口方法的調用,調用我們的攔截器。對于其他方法的調用我們讓Method Invocation處理。
Spring提供了一個一個方便的類來處理我們的大多數應用,這個類是:DelegatingIntroductionInterceptor應用這個類我們不再需要實現invoke這個方法了因為在DelegatingIntroductionInterceptor類中已經實現好了(可以參看一下源代碼)。看下面的例子:
package springinaction.chapter03.createadvice;
import java.util.Date;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
publicclass ImmutableMixin extends DelegatingIntroductionInterceptor implements Auditable
{
private Date lastModifiedDate;
public Date getLastModifiedDate()
{
returnthis.lastModifiedDate;
}//end getLastModifiedDate()
publicvoid setLastModifiedDate(Date lastModifiedDate)
{
this.lastModifiedDate = lastModifiedDate;
}//end setLastModifiedDate(...)
}//end class ImmutableMixin
DelegatingIntroductionInterceptor類也要實現你的混合類暴露的任何方法,并且將任何對這些方法的調用委托給這個混合類。因為ImmutableMixin類實現了Auditable接口,對這個接口的方法的所有調用都將調用我們的攔截器。任何其他方法委托給目標對象。如果你的攔截器要實現一個接口,你不想把它暴露成一個混合體,那么只要簡單的將這個接口傳遞給DelegatingIntroductionInterceptor類的suppressInterface()方法就可以了(該方法在DelegatingIntroductionInterceptor類的父類IntroductionInfoSupport中實現)。還有一點:如果你的混合體要改變任何目標對象方法的行為的話,你就需要實現invoke()方法。
有了自己的引入通知后,我們需要創建Advisor。因為引入通知之應用在類層次上,所以引入有他們自己的Advisor:IntroductionAdvisor。Spring也提供了一個適合大多數情況的缺省實現。名為:DefaultIntroductionAdvisor,它有一個以IntroductionInterceptor作為參數的構造函數。
在本章中使用了大量的ProxyFactoryBean來創建一個被通知的類。當你想明確的控制你的通知類如何組合時,這種是方法最好、最簡單的選擇。ProxyFactoryBean有很多控制行為的屬性。在書中作者列出了一個詳細的表格進行了說明,這里就不羅列了。詳細的ProxyFactoryBean大家也可以看一下它的源代碼。
五、小結
寫到這里我不得不打住了,再寫下去我都不知道寫的是什么了,對于新手來說AOP確實是非常抽象的概念,要想真正理解AOP并且編寫出AOP模塊,在深入理解概念的同時還要進行大量的實踐才行,從實踐中去真正的體會AOP。那么AOP有三個最核心的概念,這三個概念是:Advice、Pointcut和Advisor。Advice是你想向其他程序內部不同地方注入的代碼。Pointcut定義了需要注入Advisor的位置,這個位置通常是某個特定的類的一個public方法。Advisor是Pointcut和Advice的裝配器,是將Advice注入程序中預定義位置的代碼。按照這樣的主線再去解讀AOP的各個概念,再通過實踐理解AOP應該是比較容易的。
如果你對AOP的概念和內涵難以理解,那就多找幾本書或其他資料看看。最主要的還是看別人寫的代碼和自己大量的實踐。下面我列出幾個參考大家可以找來看看:
posted on 2007-10-23 21:44
譚明 閱讀(474)
評論(0) 編輯 收藏 所屬分類:
Spring