原貼地址:http://book.csdn.net/bookfiles/111/1001113463.shtml
一.AOP概念
介紹完
IoC
之后,我們來介紹另外一個重要的概念:
AOP(Aspect Oriented Programming)
,也就是面向方面編程的技術(shù)。
AOP
基于
IoC
基礎(chǔ),是對
OOP
的有益補充。
AOP
將應(yīng)用系統(tǒng)分為兩部分,核心業(yè)務(wù)邏輯
(
Core business concerns
)及橫向的通用邏輯,
也就是所謂的方面
Crosscutting enterprise concerns
,例如,所有大中型應(yīng)用都要涉及到的持久化管理(
Persistent
)、事務(wù)管理(
Transaction Management
)、安全管理(
Security
)、
日志管理(
Logging
)和調(diào)試管理(
Debugging
)等。
AOP
正在成為軟件開發(fā)的下一個光環(huán)。使用
AOP
,你可以將處理
aspect
的代碼注入主程序,通常主程序的主要目的并不在于處理這些
aspect
。
AOP
可以防止代碼混亂。
Spring framework
是很有前途的
AOP
技術(shù)。作為一種非侵略性的、輕型的
AOP framework
,你無需使用預(yù)編譯器或其他的元標簽,便可以在
Java
程序中使用它。這意味著開發(fā)團隊里只需一人要對付
AOP framework
,其他人還是像往常一樣編程。
6.3.1? A
OP
概念
讓我們從定義一些重要的
AOP
概念開始。
—
方面(
Aspect
):一個關(guān)注點的模塊化,這個關(guān)注點實現(xiàn)可能另外橫切多個對象。事務(wù)管理是
J2EE
應(yīng)用中一個很好的橫切關(guān)注點例子。方面用
Spring
的
Advisor
或攔截器實現(xiàn)。
—
連接點(
Joinpoint
):程序執(zhí)行過程中明確的點,如方法的調(diào)用或特定的異常被拋出。
—
通知(
Advice
):在特定的連接點,
AOP
框架執(zhí)行的動作。各種類型的通知包括“
around
”、“
before
”和“
throws
”通知。通知類型將在下面討論。許多
AOP
框架包括
Spring
都是以攔截器做通知模型,維護一個“圍繞”連接點的攔截器鏈。
—
切入點(
Pointcut
):指定一個通知將被引發(fā)的一系列連接點的集合。
AOP
框架必須允許開發(fā)者指定切入點,例如,使用正則表達式。
—
引入(
Introduction
):添加方法或字段到被通知的類。
Spring
允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現(xiàn)
IsModified
接口,來簡化緩存。
—
目標對象(
Target Object
):包含連接點的對象,也被稱作被通知或被代理對象。
—
AOP
代理(
AOP Proxy
):
AOP
框架創(chuàng)建的對象,包含通知。在
Spring
中,
AOP
代理可以是
JDK
動態(tài)代理或
CGLIB
代理。
—
編織(
Weaving
):組裝方面來創(chuàng)建一個被通知對象。這可以在編譯時完成(例如使用
AspectJ
編譯器),也可以在運行時完成。
Spring
和其他純
Java AOP
框架一樣,在運行時完成織入。
各種通知類型包括:
—
?
Around
通知:包圍一個連接點的通知,如方法調(diào)用。這是最強大的通知。
Aroud
通知在方法調(diào)用前后完成自定義的行為,它們負責(zé)選擇繼續(xù)執(zhí)行連接點或通過返回它們自己的返回值或拋出異常來短路執(zhí)行。
—
?
Before
通知:在一個連接點之前執(zhí)行的通知,但這個通知不能阻止連接點前的執(zhí)行(除非它拋出一個異常)。
—
?
Throws
通知:在方法拋出異常時執(zhí)行的通知。
Spring
提供強制類型的
Throws
通知,因此你可以書寫代碼捕獲感興趣的異常(和它的子類),不需要從
Throwable
或
Exception
強制類型轉(zhuǎn)換。
—
?
After returning
通知:在連接點正常完成后執(zhí)行的通知,例如,一個方法正常返回,沒有拋出異常。
Around
通知是最通用的通知類型。大部分基于攔截的
AOP
框架(如
Nanning
和
Jboss 4
)
只提供
Around
通知。
如同
AspectJ
,
Spring
提供所有類型的通知,我們推薦你使用最為合適的通知類型來實現(xiàn)需要的行為。例如,如果只是需要用一個方法的返回值來更新緩存,你最好實現(xiàn)一個
after returning
通知,而不是
around
通知,雖然
around
通知也能完成同樣的事情。使用最合適的通知類型使編程模型變得簡單,并能減少潛在錯誤。例如,你不需要調(diào)用在
around
通知中所需使用的
MethodInvocation
的
proceed()
方法,因此就調(diào)用失敗。
切入點的概念是
AOP
的關(guān)鍵,它使
AOP
區(qū)別于其他使用攔截的技術(shù)。切入點使通知獨立于
OO
的層次選定目標。例如,提供聲明式事務(wù)管理的
around
通知可以被應(yīng)用到跨越多個對象的一組方法上。
因此切入點構(gòu)成了
AOP
的結(jié)構(gòu)要素。
下面讓我們實現(xiàn)一個
Spring AOP
的例子。在這個例子中,我們將實現(xiàn)一個
before advice
,這意味著
advice
的代碼在被調(diào)用的
public
方法開始前被執(zhí)行。以下是這個
before advice
的實現(xiàn)代碼。
package com.ascenttech.springaop.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class TestBeforeAdvice implements MethodBeforeAdvice {
???public void before(Method m, Object[] args, Object target)
?throws Throwable {
?????System.out.println("Hello world! (by "
+ this.getClass().getName()
?+ ")");
????}
}
接口
MethodBeforeAdvice
只有一個方法
before
需要實現(xiàn),它定義了
advice
的實現(xiàn)。
before
方法共用
3
個參數(shù),它們提供了相當(dāng)豐富的信息。參數(shù)
Method m
是
advice
開始后執(zhí)行的方法,方法名稱可以用作判斷是否執(zhí)行代碼的條件。
Object[] args
是傳給被調(diào)用的
public
方法的參數(shù)數(shù)組。當(dāng)需要記日志時,參數(shù)
args
和被執(zhí)行方法的名稱都是非常有用的信息。你也可以改變傳給
m
的參數(shù),但要小心使用這個功能;編寫最初主程序的程序員并不知道主程序可能會和傳入?yún)?shù)的發(fā)生沖突。
Object target
是執(zhí)行方法
m
對象的引用。
在下面的
BeanImpl
類中,每個
public
方法調(diào)用前,都會執(zhí)行
advice
,代碼如下。
package com.ascenttech.springaop.test;
public class BeanImpl implements Bean {
?public void theMethod() {
???
?System.out.println(this.getClass().getName()
? + "." + new Exception().getStackTrace()? [0].getMethodName???()
?+??"()"??+??"says HELLO!");????
?}
}
類
BeanImpl
實現(xiàn)了下面的接口
Bean
,代碼如下。
package com.ascenttech.springaop.test;
public interface Bean {
?public void theMethod();
}
雖然不是必須使用接口,但面向接口而不是面向?qū)崿F(xiàn)編程是良好的編程實踐,
Spring
也鼓勵這樣做。
pointcut
和
advice
通過配置文件來實現(xiàn),因此,接下來你只需編寫主方法的
Java
代碼,代碼如下。
package com.ascenttech.springaop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
?public static void main(String[] args) {
? //Read the configuration file
? ApplicationContext ctx
= new FileSystemXmlApplicationContext("springconfig.xml");
? //Instantiate an object
? Bean x = (Bean) ctx.getBean("bean");
? //Execute the public method of the bean (the test)
? x.theMethod();
?}
}
我們從讀入和處理配置文件開始,接下來馬上要創(chuàng)建它。這個配置文件將作為粘合程序不同部分的“膠水”。讀入和處理配置文件后,我們會得到一個創(chuàng)建工廠
ctx
,任何一個
Spring
管理的對象都必須通過這個工廠來創(chuàng)建。對象通過工廠創(chuàng)建后便可正常使用。
僅僅用配置文件便可把程序的每一部分組裝起來,代碼如下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework. org/dtd/spring-beans.dtd">
<beans>
?<!--CONFIG-->
?<bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
????<property name="proxyInterfaces">
?????????<value>com.ascenttech.springaop.test.Bean</value>
?????</property>
? <property name="target">
???
? <ref local="beanTarget"/>
? </property>
? <property name="interceptorNames">
?? <list>
??? <value>theAdvisor</value>
?? </list>
? </property>
?</bean>?
?<!--CLASS-->?
?<bean id="beanTarget" class="com.ascenttech.springaop.test.BeanImpl"/>
?<!--ADVISOR-->
?<!--Note: An advisor assembles pointcut and advice-->
?<bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethod PointcutAdvisor">
? <property name="advice">
????? <ref local="theBeforeAdvice"/>
? </property>
? <property name="pattern">
???
?? <value>com\.ascenttech\.springaop\.test\.Bean\.theMethod</value>
? </property>
?</bean>
?<!--ADVICE-->
?<bean id="theBeforeAdvice" class="com.ascenttech.springaop.test.TestBefore Advice"/>
</beans>
4
個
bean
定義的次序并不重要。我們現(xiàn)在有了一個
advice
、一個包含了正則表達式
pointcut
的
advisor
、一個主程序類和一個配置好的接口,通過工廠
ctx
,這個接口返回自己本身實現(xiàn)的一個引用。
BeanImpl
和
TestBeforeAdvice
都是直接配置。我們用一個惟一的
ID
創(chuàng)建一個
bean
元素,并指定了一個實現(xiàn)類,這就是全部的工作。
advisor
通過
Spring framework
提供的一個
RegexMethodPointcutAdvisor
類來實現(xiàn)。我們用
advisor
的第一個屬性來指定它所需的
advice-bean
,第二個屬性則用正則表達式定義了
pointcut
,確保良好的性能和易讀性。
最后配置的是
bean
,它可以通過一個工廠來創(chuàng)建。
bean
的定義看起來比實際上要復(fù)雜。
bean
是
ProxyFactoryBean
的一個實現(xiàn),它是
Spring framework
的一部分。這個
bean
的行為通過以下的
3
個屬性來定義。
—
屬性
proxyInterface
定義了接口類。
—
屬性
target
指向本地配置的一個
bean
,這個
bean
返回一個接口的實現(xiàn)。
—
屬性
interceptorNames
是惟一允許定義一個值列表的屬性,這個列表包含所有需要在
beanTarget
上執(zhí)行的
advisor
。注意,
advisor
列表的次序是非常重要的。
二.Spring的切入點,通知,advisor
讓我們看看
Spring
如何處理切入點這個重要的概念。
1
.概念
Spring
的切入點模型能夠使切入點獨立于通知類型被重用。
同樣的切入點有可能接受不同的通知。
org.springframework.aop.Pointcut
接口是重要的接口,用來指定通知到特定的類和方法目標,完整的接口定義如下。
public interface Pointcut {
??? ClassFilter getClassFilter();
??? MethodMatcher getMethodMatcher();
}
將
Pointcut
接口分成兩個部分有利于重用類和方法的匹配部分,并且組合細粒度的操作(如和另一個方法匹配器執(zhí)行一個“并”的操作)。
ClassFilter
接口被用來將切入點限制到一個給定的目標類的集合。如果
matches()
永遠返回
true
,所有的目標類都將被匹配。
public interface ClassFilter {
?? boolean matches(Class clazz);
}
MethodMatcher
接口通常更加重要,完整的接口如下。
public interface MethodMatcher {
??? boolean matches(Method m, Class targetClass);
??? boolean isRuntime();
??? boolean matches(Method m, Class targetClass, Object[] args);
}
matches(Method, Class)
方法被用來測試這個切入點是否匹配目標類的給定方法。這個測試可以在
AOP
代理創(chuàng)建的時候執(zhí)行,避免在所有方法調(diào)用時都需要進行測試。如果
2
個參數(shù)的匹配方法對某個方法返回
true
,并且
MethodMatcher
的
isRuntime()
也返回
true
,那么
3
個參數(shù)的匹配方法將在每次方法調(diào)用的時候被調(diào)用。這使切入點能夠在目標通知被執(zhí)行之前立即查看傳遞給方法調(diào)用的參數(shù)。
大部分
MethodMatcher
都是靜態(tài)的,意味著
isRuntime()
方法返回
false
。這種情況下,
3
個參數(shù)的匹配方法永遠不會被調(diào)用。
2
.切入點的運算
Spring
支持的切入點的運算有并和交。
并表示只要任何一個切入點匹配的方法。交表示兩個切入點都要匹配的方法。并通常比較有用。
切入點可以用
org.springframework.aop.support.Pointcuts
類的靜態(tài)方法來組合,或者使用同一個包中的
ComposablePointcut
類。
3
.實用切入點實現(xiàn)
Spring
提供幾個實用的切入點實現(xiàn),一些可以直接使用,另一些需要子類化來實現(xiàn)應(yīng)用相關(guān)的切入點。
(
1
)靜態(tài)切入點
靜態(tài)切入點只基于方法和目標類,而不考慮方法的參數(shù)。靜態(tài)切入點足夠滿足大多數(shù)情況的使用。
Spring
可以只在方法第一次被調(diào)用的時候計算靜態(tài)切入點,不需要在每次方法調(diào)用的時候計算。
讓我們看一下
Spring
提供的一些靜態(tài)切入點的實現(xiàn)。
—
正則表達式切入點
一個很顯然的指定靜態(tài)切入點的方法是正則表達式。除了
Spring
以外,其他的
AOP
框架也實現(xiàn)了這一點。
org.springframework.aop.support.RegexpMethodPointcut
是一個通用的正則表達式切入點,它使用
Perl 5
的正則表達式的語法。
使用這個類你可以定義一個模式的列表。如果任何一個匹配,那個切入點將被計算成
true
(所以,結(jié)果相當(dāng)于是這些切入點的并集)。
用法如下。
<bean id="settersAndAbsquatulatePointcut"?
??? class="org.springframework.aop.support.RegexpMethodPointcut">
??? <property name="patterns">
??????? <list>
????????????<value>.*get.*</value>
??????????? <value>.*absquatulate</value>
??????? </list>
??? </property>
</bean>
RegexpMethodPointcut
一個實用子類,
RegexpMethodPointcutAdvisor
允許我們同時引用一個通知(通知可以是攔截器、
before
通知、
throws
通知等)。這簡化了
bean
的裝配,因為一個
bean
可以同時當(dāng)作切入點和通知,如下所示。
<bean id="settersAndAbsquatulateAdvisor"?
??? class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
??? <property name="interceptor">
??????? <ref local="beanNameOfAopAllianceInterceptor"/>
??? </property>
??? <property name="patterns">
??????? <list>
??????????? <value>.*get.*</value>
??????????? <value>.*absquatulate</value>
??????? </list>
??? </property>
</bean>
RegexpMethodPointcutAdvisor
可以用于任何通知類型。
—
屬性驅(qū)動的切入點
一類重要的靜態(tài)切入點是元數(shù)據(jù)驅(qū)動的切入點。它使用元數(shù)據(jù)屬性的值,典型地,使用源代碼級元數(shù)據(jù)。
(
2
)動態(tài)切入點
動態(tài)切入點的演算代價比靜態(tài)切入點高得多。它們不僅考慮靜態(tài)信息,還要考慮方法的參數(shù)。這意味著它們必須在每次方法調(diào)用的時候都被計算,并且不能緩存結(jié)果,因為參數(shù)是變化的。
—
控制流切入點
Spring
的控制流切入點概念上和
AspectJ
的
cflow
切入點一致,雖然沒有其那么強大(當(dāng)前沒有辦法指定一個切入點在另一個切入點后執(zhí)行)。一個控制流切入點匹配當(dāng)前的調(diào)用棧。例如,連接點被
com.mycompany.web
包或者
SomeCaller
類中一個方法調(diào)用的時候,觸發(fā)該切入點。控制流切入點的實現(xiàn)類是
org.springframework.aop.support.ControlFlowPointcut
。
4
.切入點超類
Spring
提供非常實用的切入點的超類幫助你實現(xiàn)你自己的切入點。
因為靜態(tài)切入點非常實用,你很可能子類化
StaticMethodMatcherPointcut
,如下所示。
這只需要實現(xiàn)一個抽象方法(雖然可以改寫其他的方法來自定義行為)。
class TestStaticPointcut extends StaticMethodMatcherPointcut {
??? public boolean matches(Method m, Class targetClass) {
??????? // return true if custom criteria match
??? }
}
當(dāng)然也有動態(tài)切入點的超類。
Spring 1.0 RC2
或以上版本,自定義切入點可以用于任何類型的通知。
5
.自定義切入點
因為
Spring
中的切入點是
Java
類,而不是語言特性(如
AspectJ
),因此可以定義自定義切入點,無論靜態(tài)還是動態(tài)。但是,沒有直接支持用
AspectJ
語法書寫的復(fù)雜的切入點表達式。不過,
Spring
的自定義切入點也可以任意的復(fù)雜。
現(xiàn)在讓我們看看
Spring AOP
是如何處理通知的。
1
.通知的生命周期
Spring
的通知可以跨越多個被通知對象共享,或者每個被通知對象有自己的通知。這分別對應(yīng)
per-class
或
per-instance
通知。
Per-class
通知使用最為廣泛。它適合于通用的通知,如事務(wù)
adisor
。它們不依賴被代理的對象的狀態(tài),也不添加新的狀態(tài)。它們僅僅作用于方法和方法的參數(shù)。
Per-instance
通知適合于導(dǎo)入,來支持混入(
mixin
)。在這種情況下,通知添加狀態(tài)到被代理的對象。
可以在同一個
AOP
代理中混合使用共享和
per-instance
通知。
2
.
Spring
中通知類型
Spring
提供幾種現(xiàn)成的通知類型并可擴展提供任意的通知類型。讓我們看看基本概念和標準的通知類型。
(
1
)
Interception around advice
Spring
中最基本的通知類型是
interception around advice
。
Spring
使用方法攔截器的
around
通知是和
AOP
聯(lián)盟接口兼容的。實現(xiàn)
around
通知的類需要實現(xiàn)接口
MethodInterceptor
。
public interface MethodInterceptor extends Interceptor {
?
?? Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()
方法的
MethodInvocation
參數(shù)暴露將被調(diào)用的方法、目標連接點、
AOP
代理和傳遞給被調(diào)用方法的參數(shù)。
invoke()
方法應(yīng)該返回調(diào)用的結(jié)果:連接點的返回值。
一個簡單的
MethodInterceptor
實現(xiàn)看起來如下。
public class DebugInterceptor implements MethodInterceptor {
??? public Object invoke(MethodInvocation invocation) throws Throwable {
??????? System.out.println("Before: invocation=[" + invocation + "]");
??????? Object rval = invocation.proceed();
??????? System.out.println("Invocation returned");
??????? return rval;
??? }
}
注意
MethodInvocation
的
proceed()
方法的調(diào)用。這個調(diào)用會應(yīng)用到目標連接點的攔截器鏈中的每一個攔截器。大部分攔截器會調(diào)用這個方法,并返回它的返回值。但是,一個
MethodInterceptor
,和任何
around
通知一樣,可以返回不同的值或者拋出一個異常,而不調(diào)用
proceed
方法。但是,沒有好的原因你要這么做。
MethodInterceptor
提供了和其他
AOP
聯(lián)盟的兼容實現(xiàn)的交互能力。這一節(jié)下面要討論的其他的通知類型實現(xiàn)了
AOP
公共的概念,但是以
Spring
特定的方式。雖然使用特定通知類型有很多優(yōu)點,但如果你需要在其他的
AOP
框架中使用,請堅持使用
MethodInterceptor around
通知類型。注意,目前切入點不能和其他框架交互操作,并且
AOP
聯(lián)盟目前也沒有定義切入點接口。
2
)
Before
通知
Before
通知
是一種簡單的通知類型。
這個通知不需要一個
MethodInvocation
對象,因為它只在進入一個方法前被調(diào)用。
Before
通知的主要優(yōu)點是它不需要調(diào)用
proceed()
方法,因此沒有無意中忘掉繼續(xù)執(zhí)行攔截器鏈的可能性。
MethodBeforeAdvice
接口如下所示(
Spring
的
API
設(shè)計允許成員變量的
Before
通知,雖然一般的對象都可以應(yīng)用成員變量攔截,但
Spring
有可能永遠不會實現(xiàn)它)。
public interface MethodBeforeAdvice extends BeforeAdvice {
??? void before(Method m, Object[] args, Object target) throws Throwable;
}
注意,返回類型是
void
。
Before
通知可以在連接點執(zhí)行之前插入自定義的行為,但是不能改變返回值。如果一個
Before
通知拋出一個異常,這將中斷攔截器鏈的進一步執(zhí)行。這個異常將沿著攔截器鏈后退著向上傳播。如果這個異常是
unchecked
的,或者出現(xiàn)在被調(diào)用的方法的簽名中,它將會被直接傳遞給客戶代碼;否則,它將被
AOP
代理包裝到一個
unchecked
的異常里。
下面是
Spring
中一個
Before
通知的例子,這個例子計數(shù)所有正常返回的方法。
public class CountingBeforeAdvice implements MethodBeforeAdvice {
??? private int count;
??? public void before(Method m, Object[] args, Object target) throws Throwable {
??????? ++count;
??? }
??? public int getCount() {?
??????? return count;?
??? }
}
Before
通知可以被用于任何類型的切入點。
(
3
)
Throws
通知
如果連接點拋出異常,
Throws
通知
在連接點返回后被調(diào)用。
Spring
提供強類型的
Throws
通知。注意,這意味著
org.springframework.aop.ThrowsAdvice
接口不包含任何方法,它是一個標記接口,標識給定的對象實現(xiàn)了一個或多個強類型的
Throws
通知方法。這些方法形式如下。
afterThrowing([Method], [args], [target], subclassOfThrowable)
只有最后一個參數(shù)是必需的。這樣從
1
個參數(shù)到
4
個參數(shù),依賴于通知是否對方法和方法的參數(shù)感興趣。下面是
Throws
通知的例子。
如果拋出
RemoteException
異常(包括子類),這個通知會被調(diào)用。
public? class RemoteThrowsAdvice implements ThrowsAdvice {
??? public void afterThrowing(RemoteException ex) throws Throwable {
??????? // Do something with remote exception
??? }
}
如果拋出
ServletException
異常,下面的通知會被調(diào)用。和上面的通知不一樣,它聲明了
4
個參數(shù),所以它可以訪問被調(diào)用的方法、方法的參數(shù)和目標對象。
public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
??? public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
??????? // Do something will all arguments
?? }
}
最后一個例子演示了如何在一個類中使用兩個方法來同時處理
RemoteException
和
ServletException
異常。任意個數(shù)的
throws
方法可以被組合在一個類中。
public static class CombinedThrowsAdvice implements ThrowsAdvice {
??? public void afterThrowing(RemoteException ex) throws Throwable {
??????? // Do something with remote exception
??? }
??? public void afterThrowing(Method m, Object[] args, Object target, ServletException ex){
?
?????? // Do something will all arguments
??? }
}
Throws
通知可被用于任何類型的切入點。
(
4
)
After Returning
通知
Spring
中的
After Returning
通知必須實現(xiàn)
org.springframework.aop.AfterReturningAdvice
接口,如下所示。
public interface AfterReturningAdvice extends Advice {
??? void afterReturning(Object returnValue, Method m, Object[] args, Object target)?
throws Throwable;
}
After Returning
通知可以訪問返回值(不能改變)、被調(diào)用的方法、方法的參數(shù)和目標對象。
下面的
After Returning
通知統(tǒng)計所有成功的沒有拋出異常的方法調(diào)用。
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
??? private int count;
??? public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
??????? ++count;
??? }
??? public int getCount() {
??????? return count;
????}
}
這方法不改變執(zhí)行路徑。如果它拋出一個異常,這個異常而不是返回值將被沿著攔截器鏈向上拋出。
After returning
通知可被用于任何類型的切入點。
(
5
)
Introduction
通知
Spring
將
Introduction
通知看作一種特殊類型的攔截通知。
Introduction
需要實現(xiàn)
IntroductionAdvisor
和
IntroductionInterceptor
接口。
public interface IntroductionInterceptor extends MethodInterceptor {
??? boolean implementsInterface(Class intf);
}
繼承自
AOP
聯(lián)盟
MethodInterceptor
接口的
invoke()
方法必須實現(xiàn)導(dǎo)入,也就是說,如果被調(diào)用的方法是在導(dǎo)入的接口中,導(dǎo)入攔截器負責(zé)處理這個方法調(diào)用,它不能調(diào)用
proceed()
方法。
Introduction
通知不能被用于任何切入點,因為它只能作用于類層次上,而不是方法。你可以只用
InterceptionIntroductionAdvisor
來實現(xiàn)導(dǎo)入通知,它有下面的方法。
public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {
??? ClassFilter getClassFilter();
??? IntroductionInterceptor getIntroductionInterceptor();
??? Class[] getInterfaces();
}
這里沒有
MethodMatcher
,因此也沒有和導(dǎo)入通知關(guān)聯(lián)的切入點,只有類過濾是合乎邏輯的。
getInterfaces()
方法返回
advisor
導(dǎo)入的接口。
讓我們看看一個來自
Spring
測試套件中的簡單例子。我們假設(shè)想要導(dǎo)入下面的接口到一個或者多個對象中。
public interface Lockable {
??? void lock();
??? void unlock();
??? boolean locked();
}
在這個例子中,我們想要能夠?qū)⒈煌ㄖ獙ο箢愋娃D(zhuǎn)換為
Lockable
,不管它們的類型,并且調(diào)用
lock
和
unlock
方法。如果我們調(diào)用
lock()
方法,我們希望所有
setter
方法拋出
LockedException
異常。這樣我們能添加一個方面使對象
不可變,而它們不需要知道這一點,這是一個很好的
AOP
例子。
首先,我們需要一個做大量轉(zhuǎn)化的
IntroductionInterceptor
。在這里,我們繼承
org.spring framework.aop.support.DelegatingIntroductionInterceptor
實用類。我們可以直接實現(xiàn)
Introduction
Interceptor
接口,但是,大多數(shù)情況下,
DelegatingIntroductionInterceptor
是最合適的。
DelegatingIntroductionInterceptor
的設(shè)計是將導(dǎo)入委托到真正實現(xiàn)導(dǎo)入接口的接口,隱藏完成這些工作的攔截器。委托可以使用構(gòu)造方法參數(shù)設(shè)置到任何對象中,默認的委托就是自己(當(dāng)無參數(shù)的構(gòu)造方法被使用時)。這樣,在下面的例子里,委托是
DelegatingIntroduction Interceptor
的子類
LockMixin
。給定一個委托(默認是自身)的
DelegatingIntroductionIntercepto
r
實例尋找被這個委托(而不是
IntroductionInterceptor
)實現(xiàn)的所有接口,并支持它們中任何一個導(dǎo)入。子類如
LockMixin
也可能調(diào)用
suppressInterflace (Class intf)
方法隱藏不應(yīng)暴露的接口。然而,不管
IntroductionInterceptor
準備支持多少接口,
IntroductionAdvisor
將控制哪個接口將被實際暴露。一個導(dǎo)入的接口將隱藏目標的同一個接口的所有實現(xiàn)。
這樣,
LockMixin
繼承
DelegatingIntroductionInterceptor
并自己實現(xiàn)
Lockable
。父類自動選擇支持導(dǎo)入的
Lockable
,所以我們不需要指定它。用這種方法我們可以導(dǎo)入任意數(shù)量的接口。
注意
locked
實例變量的使用,這有效地添加了額外的狀態(tài)到目標對象。
public class LockMixin extends DelegatingIntroductionInterceptor
implements Lockable {
??? private boolean locked;
??? public void lock() {
?????? this.locked = true;
??? }
??? public void unlock() {
??????? this.locked = false;
??? }
??? public boolean locked() {
??????? return this.locked;
??? }
??? public Object invoke(MethodInvocation invocation) throws Throwable {
?????? if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
??????????? throw new LockedException();
??????? return super.invoke(invocation);
???
???}
}
通常不要需要改寫
invoke()
方法,實現(xiàn)
DelegatingIntroductionInterceptor
就足夠了,如果是導(dǎo)入的方法,
DelegatingIntroductionInterceptor
實現(xiàn)會調(diào)用委托方法,否則繼續(xù)沿著連接點處理。在現(xiàn)在的情況下,我們需要添加一個檢查:在上鎖狀態(tài)下不能調(diào)用
setter
方法。
所需的導(dǎo)入
advisor
是很簡單的。只有保存一個獨立的
LockMixin
實例,并指定導(dǎo)入的接口,在這里就是
Lockable
。一個稍微復(fù)雜一點例子可能需要一個導(dǎo)入攔截器(可以定義成
prototype
)的引用:在這種情況下,
LockMixin
沒有相關(guān)配置,所以我們簡單地使用
new
來創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
??? public LockMixinAdvisor() {
??????? super(new LockMixin(), Lockable.class);
??? }
}
我們可以非常簡單地使用這個
advisor
,它不需要任何配置(但是,有一點是必要的,就是不可能在沒有
IntroductionAdvisor
的情況下使用
IntroductionInterceptor
)。和導(dǎo)入一樣,通常
advisor
必須是針對每個實例的,并且是有狀態(tài)的。我們會有不同的
LockMixinAdvisor
每個被通知對象,會有不同的
LockMixin
。
advisor
組成了被通知對象的狀態(tài)的一部分。
和其他
advisor
一樣,我們可以使用
Advised.addAdvisor()
方法以編程地方式使用這種
advisor
,或者在
XML
中配置(推薦這種方式)。下面將討論所有代理創(chuàng)建,包括“自動代理創(chuàng)建者”,選擇代理創(chuàng)建以正確地處理導(dǎo)入和有狀態(tài)的混入。
在
Spring
中,一個
advisor
就是一個
aspect
的完整的模塊化表示。一般地,一個
advisor
包括通知和切入點。
撇開導(dǎo)入這種特殊情況,任何
advisor
可被用于任何通知。
org.springframework.aop. support.DefaultPointcutAdvisor
是最通用的
advisor
類。例如,它可以和
MethodInterceptor
、
BeforeAdvice
或
ThrowsAdvice
一起使用。
Spring
中可以將
advisor
和通知混合在一個
AOP
代理中。例如,你可以在一個代理配置中使用一個對
Around
通知、
Throws
通知和
Before
通知的攔截:
Spring
將自動創(chuàng)建必要的攔截器鏈。
三.用ProxyFactoryBean創(chuàng)建AOP代理
如果你在為你的業(yè)務(wù)對象使用
Spring
的
IoC
容器(例如,
ApplicationContext
或
BeanFactory
),你應(yīng)該會或者你愿意會使用
Spring
的
AOP FactoryBean
(記住,
factory bean
引入了一個間接層,它能創(chuàng)建不同類型的對象)。
在
spring
中創(chuàng)建
AOP proxy
的基本途徑是使用
org.springframework.aop.framework. ProxyFactoryBean
。這樣可以對
pointcut
和
advice
做精確控制。但是,如果你不需要這種控制,那些簡單的選擇可能更適合你。
1
.基本概要
ProxyFactoryBean
和其他
Spring
的
FactoryBean
實現(xiàn)一樣,引入一個間接的層次。如果你定義一個名字為
foo
的
ProxyFactoryBean
,引用
foo
的對象所看到的不是
ProxyFactoryBean
實例本身,而是由實現(xiàn)
ProxyFactoryBean
的類的
getObject()
方法所創(chuàng)建的對象。這個方法將創(chuàng)建一個包裝了目標對象的
AOP
代理。
使用
ProxyFactoryBean
或者其他
IoC
可知的類來創(chuàng)建
AOP
代理的最重要的優(yōu)點之一是
IoC
可以管理通知和切入點。這是一個非常的強大的功能,能夠?qū)崿F(xiàn)其他
AOP
框架很難實現(xiàn)的特定的方法。例如,一個通知本身可以引用應(yīng)用對象(除了目標對象,它在任何
AOP
框架中都可以引用應(yīng)用對象),這完全得益于依賴注入所提供的可插入性。
2
.
JavaBean
的屬性
類似于
Spring
提供的絕大部分
FactoryBean
實現(xiàn)一樣,
ProxyFactoryBean
也是一個
javabean
,我們可以利用它的屬性來指定你將要代理的目標,指定是否使用
CGLIB
。
一些關(guān)鍵屬性來自
org.springframework.aop.framework.ProxyConfig
,它是所有
AOP
代理工廠的父類。這些關(guān)鍵屬性包括:
—
?
proxyTargetClass
:如果我們應(yīng)該代理目標類,而不是接口,這個屬性的值為
true
。如果這是
true
,我們需要使用
CGLIB
。
—
?
optimize
:是否使用強優(yōu)化來創(chuàng)建代理。不要使用這個設(shè)置,除非你了解相關(guān)的
AOP
代理是如何處理優(yōu)化的。目前這只對
CGLIB
代理有效,對
JDK
動態(tài)代理無效(默認)。
—
?
frozen
:是否禁止通知的改變,一旦代理工廠已經(jīng)配置。默認是
false
。
—
?
exposeProxy
:當(dāng)前代理是否要暴露在
ThreadLocal
中,以便它可以被目標對象訪問(它可以通過
MethodInvocation
得到,不需要
ThreadLocal
)。如果一個目標需要獲得它的代理,并且
exposeProxy
的值是
ture
,可以使用
AopContext.currentProxy()
方法。
—
?
AopProxyFactory
:所使用的
AopProxyFactory
具體實現(xiàn)。這個參數(shù)提供了一條途徑來定義是否使用動態(tài)代理、
CGLIB
還是其他代理策略。默認實現(xiàn)將適當(dāng)?shù)剡x擇動態(tài)代理或
CGLIB
。一般不需要使用這個屬性;它的意圖是允許
Spring 1.1
使用另外新的代理類型。
其他
ProxyFactoryBean
特定的屬性包括:
—
?
proxyInterfaces
:接口名稱的字符串?dāng)?shù)組。如果這個沒有提供,
CGLIB
代理將被用于目標類。
—
?
interceptorNames: Advisor
、
interceptor
或其他被應(yīng)用的通知名稱的字符串?dāng)?shù)組。順序是很重要的。這里的名稱是當(dāng)前工廠中
bean
的名稱,包括來自祖先工廠的
bean
的名稱。
—
?
Singleton
:工廠是否返回一個單獨的對象,無論
getObject()
被調(diào)用多少次。許多
FactoryBean
的實現(xiàn)提供這個方法,默認值是
true
。如果你想要使用有狀態(tài)的通知,(例如,用于有狀態(tài)的
mixin
)將這個值設(shè)為
false
,使用
prototype
通知。
3
.代理接口
讓我們來看一個簡單的
ProxyFactoryBean
的實際例子。這個例子涉及到一個將被代理的目標
bean
,在這個例子里,這個
bean
被定義為“
personTarget
”;一個
advisor
和一個
interceptor
來提供
advice
;一個
AOP
代理
bean
定義,該
bean
指定目標對象(這里是
personTarget bean
),代理接口和使用的
advice
。
<bean id="personTarget" class="com.mycompany.PersonImpl">
??? <property name="name"><value>Tony</value></property>
??? <property name="age"><value>51</value></property>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
??? <property name="someProperty"><value>Custom string property value </value> </property>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor. NopInterceptor">
</bean>
<bean id="person"?
??? class="org.springframework.aop.framework.ProxyFactoryBean">
??? <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
????<property name="target"><ref local="personTarget"/></property>
??? <property name="interceptorNames">
??????? <list>
??????????? <value>myAdvisor</value>
??????????? <value>debugInterceptor</value>
??????? </list>
??? </property>
</bean>
請注意,
person bean
的
interceptorNames
屬性提供一個
String
列表,列出的是該
ProxyFactoryBean
使用的,在當(dāng)前
bean
工廠定義的
interceptor
或者
advisor
的名字(
advisor
、
interceptor
、
before
、
after returning
和
throws advice
對象皆可)。
Advisor
在該列表中的次序很重要。
你也許會對該列表為什么不采用
bean
的引用存有疑問。原因就在于如果
ProxyFactory Bean
的
singleton
屬性被設(shè)置為
false
,那么
bean
工廠必須能返回多個獨立的代理實例。如果有任何一個
advisor
本身是
prototype
的,那么它就需要返回獨立的實例,也就是有必要從
bean
工廠獲取
advisor
的不同實例,
bean
的引用在這里顯然是不夠的。
上面定義的“
person
”
bean
定義可以作為
Person
接口的實現(xiàn)來使用,如下所示。
Person person = (Person) factory.getBean("person");
在同一個
IoC
的上下文中,其他的
bean
可以依賴于
Person
接口,就像依賴于一個普通的
java
對象一樣。
<bean id="personUser" class="com.mycompany.PersonUser">
??? <property name="person"><ref local="person" /></property>
</bean>
在這個例子里,
PersonUser
類暴露了一個類型為
Person
的屬性。只要是在用到該屬性的地方,
AOP
代理都能透明地替代一個真實的
Person
實現(xiàn)。但是,這個類可能是一個動態(tài)代理類。也就是有可能把它類型轉(zhuǎn)換為一個
Advised
接口(該接口在下面的章節(jié)中論述)。
4
.代理類
如果你需要代理的是類,而不是一個或多個接口,又該怎么辦呢
?
想象一下我們上面的例子,如果沒有
Person
接口,我們需要通知一個叫
Person
的類,而且該類沒有實現(xiàn)任何業(yè)務(wù)接口。在這種情況下,你可以配置
Spring
使用
CGLIB
代理,而不是動態(tài)
代理。你只要在上面的
ProxyFactoryBean
定義中把它的
proxyTargetClass
屬性改成
true
就可以。
只要你愿意,即使在有接口的情況下,你也可以強迫
Spring
使用
CGLIB
代理。
CGLIB
代理是通過在運行期產(chǎn)生目標類的子類來進行工作的。
Spring
可以配置這個生成的子類,來代理原始目標類的方法調(diào)用。這個子類是用
Decorator
設(shè)計模式置入到
advice
中的。
CGLIB
代理對于用戶來說應(yīng)該是透明的。然而,還有以下一些因素需要考慮。
(
1
)
Final
方法不能被通知,因為不能被重寫。
(
2
)你需要在你的
classpath
中包括
CGLIB
的二進制代碼,而動態(tài)代理對任何
JDK
都是可用的。
(
3
)
CGLIB
和動態(tài)代理在性能上有微小的區(qū)別,對
Spring 1.0
來說,后者稍快。另外,以后可能會有變化。在這種情況下,性能不是決定性因素。
posted on 2006-11-01 11:17
OMG 閱讀(1392)
評論(0) 編輯 收藏 所屬分類:
Spring