在閱讀此文之前請(qǐng)你熟悉一些IOC的知識(shí),同時(shí)了解AOP的概念。
在
Spring
中所有的通知都是以
Java
類(lèi)的形式編寫(xiě)的。
Spring
是采用運(yùn)行期的方式來(lái)將切面織入到系統(tǒng)中的。
代理 Bean 只有在第一次被應(yīng)用系統(tǒng)需要的時(shí)候才被創(chuàng)建。 Spring 有兩種方式創(chuàng)建代理: Proxy 類(lèi)創(chuàng)建代理 ( 實(shí)現(xiàn)過(guò)接口的目標(biāo)類(lèi) ) 和運(yùn)用 CGLIB 庫(kù)創(chuàng)建代理 ( 沒(méi)有實(shí)現(xiàn)過(guò)任何接口的目標(biāo)類(lèi) ) 。需要注意兩點(diǎn): 1 、對(duì)接口創(chuàng)建代理優(yōu)于對(duì)類(lèi)創(chuàng)建代理,因?yàn)檫@樣會(huì)產(chǎn)生更加松耦合的系統(tǒng), 2 、標(biāo)記為 final 的方法是不能被通知的,因?yàn)?/span> Spring 在實(shí)現(xiàn)的時(shí)候是為目標(biāo)類(lèi)產(chǎn)生子類(lèi)。
Spring 只支持方法聯(lián)接點(diǎn)。
Spring
中通知的類(lèi)型:
1 、 Around???? org.aopalliance.intercept.MethodInterceptor??????? 欄截對(duì)目標(biāo)對(duì)象方法的調(diào)用
2 、 Before?????? org.springframework.aop.MethodBeforAdvice???? 在目標(biāo)方法被調(diào)用之前調(diào)用
3 、 After ?????? org.springframework.aop.AfterReturningAdvice? 當(dāng)目標(biāo)方法被調(diào)用之后調(diào)用
4 、 Thorws???? org.springframework.aop.ThrowsAdvice???? ?????? 當(dāng)目標(biāo)方法拋出異常時(shí)調(diào)用
前置通知 實(shí)現(xiàn) MethodBeforeAdvice 接口,該接口有以下方法:
void befor(Method method,Object []args,Object target) throws Throwable;
例:
public class WelcomeAdvice implements MethodBeforeAdvice{
?????? public void before(Method method,Object []args,Object target){
????????????? System.out.println("Hello,"+((Customer)args[0]).getName());
?????? }
}
配置文件如下:
<beans>
<bean id="aaaTargetObject" class="AAA"/>?? // 目標(biāo)類(lèi) , 這是一個(gè)實(shí)現(xiàn)某個(gè)接口的類(lèi)
<bean id="welcomeAdvice" class="WelcomeAdvice"/>? // 通知類(lèi)
<bean id="aaa" class="org.springframework.aop.framework.proxyFactoryBean">
?????? <property name="interceptorNames">
????????????? <list>
???????????????????? <value>welcomAdvice</value>
????????????? </list>
?????? </property>
?????? <property name="target">
????????????? <ref bean="aaaTargetObject"/>
?????? </property>
</bean>
</beans>
后置通知 實(shí)現(xiàn) AfterReturningAdvice 接口,該接口有以下方法:
void afterReturning(Object returnValue,Method method,Object []args,Object target) throws Throwable;
例:
public class ThankYouAdvice implements AfterReturningAdvice{
?????? public void afterReturning(Object returnValue,Method method,Object []args,Object target){
????????????? System.out.println("Thank you, come again.");
?????? }
}
在前置通知和后置通知中都不能改變參數(shù)中傳進(jìn)來(lái)的值,改變執(zhí)行流程的惟一方法就是拋出異常。
環(huán)繞通知 實(shí)現(xiàn) MethodInterceptor 接口,該接口有以下方法:
Object invoke(MethodInvocation invocation) throws Throwable;
環(huán)繞通知能夠控制目標(biāo)方法是否真的被調(diào)用,通過(guò)調(diào)用 MethodInvocation.proceed() 方法來(lái)調(diào)用目標(biāo)方法。它還可以讓你控制返回的對(duì)象。
例:
public class ExampleAroundInterceptor implements MethodInterceptor{
?????? public Object invoke(MethodInvocation invocation) throws Throwable{
????????????? // 調(diào)用之前可以做一些處理
????????????? Object returnObject=invocation.proceed();// 調(diào)用目標(biāo)方法
????????????? // 調(diào)用之后還可以做一些處理
????????????? return returnObject;
?????? }
}
異常通知 實(shí)現(xiàn) ThrowsAdvice 接口,此接口是一個(gè)標(biāo)示接口,沒(méi)有定義必須實(shí)現(xiàn)的方法,但是下面兩個(gè)方法中必須實(shí)現(xiàn)一個(gè):
void afterThrowing(Throwable throwable);
void afterThrowing(Method method,Object []args,Object target,Throwable throwable);
ThrowsAdvice 要處理的異常取決于你的方法定義的異常類(lèi)型,在一個(gè)實(shí)現(xiàn)類(lèi)中,也可以實(shí)現(xiàn)多個(gè) afterThrowing 方法,根據(jù)拋出的異常類(lèi)型調(diào)用適當(dāng)?shù)姆椒ā?/span>
引入通知 給目標(biāo)對(duì)象添加新的方法 ( 以及屬性 ) 。
定義切入點(diǎn)
切入點(diǎn) 就是應(yīng)用通知的地方,切入點(diǎn)決定了一個(gè)特定的類(lèi)的特定方法是否滿(mǎn)足一條特定的規(guī)則,如果一個(gè)方法符合的話(huà),通知就應(yīng)用到該方法上。 Spring 的切入點(diǎn)可以讓我們以一種靈活的方式定義在什么地方將通知織入到我們的類(lèi)中。
切入點(diǎn)的核心接口是 Pointcut 接口,同時(shí)它也需要兩個(gè)接口支持,如下:
public interface Pointcut{
?????? ClassFilter getClassFilter(); // 決定一個(gè)類(lèi)是否符合要求
?????? MethodMatcher getMethodMatcher(); // 決定一個(gè)方法是否符合要求
}
public interface ClassFilter{
?????? boolean matches(Class clazz);
}
此接口總是包含一個(gè)簡(jiǎn)單的 ClassFilter 接口的實(shí)現(xiàn) --ClassFilter.TRUE ,它是規(guī)范的適合任何類(lèi)的 ClassFilter 實(shí)例,它適合于只根據(jù)方法決定什么時(shí)候符合要求的切入點(diǎn)。
public interface MethodMatcher{
?????? boolean matches(Method m,class targetClass);// 靜態(tài)的決定一個(gè)方法是否被通知織入
?????? public boolean isRuntime();// 決定是靜態(tài)的還是動(dòng)態(tài)的織入
?????? public boolean mathes(Method m,Class targetClass,Object []args);// 動(dòng)態(tài)的決定一個(gè)方法是否被通知織入,系統(tǒng)開(kāi)銷(xiāo)會(huì)增加,不推薦使用
}
大多數(shù)的切面是由定義切面行為的通知和定義切面在什么地方執(zhí)行的切入點(diǎn)給合而成的, Spring 為此提供了 Advisor ,它把通知和切入點(diǎn)組合到一個(gè)對(duì)象中。接口 PointcutAdvisor 提供了這些功能,接口如下:
public interface PointcutAdvisor{
?????? Pointcut getPointcut();
?????? Advice getAdvice();
}
大多數(shù) Spring 自帶的切入點(diǎn)都有一個(gè)對(duì)應(yīng)的 PointcutAdvisor 。這樣方便你在一個(gè)地方定義通知和切入點(diǎn)。
使用 Spring 的靜態(tài)切入點(diǎn) :
Spring 為創(chuàng)建靜態(tài)切入點(diǎn)提供了方便的父類(lèi): StaticMethodMatcherPointcut 。如果想自定義靜態(tài)切入點(diǎn)的話(huà),繼承這個(gè)類(lèi),并實(shí)現(xiàn) isMatch 方法就 OK 了。
Spring 提供了一個(gè)靜態(tài)切入點(diǎn)的實(shí)現(xiàn)類(lèi): NameMatchMethodPointcut 。它有如下兩個(gè)方法:
Public void setMappedName(String);
Public void setMappedNames(String []);
這個(gè)類(lèi)通過(guò)名字映射,上面兩個(gè)方法的參數(shù)中均可以使用通配符 * 。
規(guī)則表達(dá)式切入點(diǎn), Spring 提供了 RegexpMethodPointcut 讓你利用正則表達(dá)式的力量來(lái)定義切入點(diǎn)。
符號(hào) |
描述 |
示例 |
. |
匹配任何單個(gè)字符 |
setFoo. 匹配 setFooB, 但不匹配 setFoo 或 setFooBar |
+ |
匹配前一個(gè)字符一次或多次 |
setFoo.+ 匹配 setFooBar 和 setFooB, 但不匹配 setFoo |
* |
匹配前一個(gè)字符 0 次或多次 |
setFoo.* 匹配 setFoo,setFooB 和 setFooBar |
\ |
匹配任何正則表達(dá)式符號(hào) |
\.setFoo. 匹配 bar.setFoo, 但不匹配 setFoo |
如果你想匹配所有 setXxx 方法,我們需要使用 .*set*. 模版 ( 第一個(gè)通配符匹配任何類(lèi)名。筆者認(rèn)為此處《 Spring in action 》一書(shū)中有誤,我認(rèn)為此處應(yīng)為 .*set.*) 。如果使用 RegexpMethodPointcut ,你需要在你的應(yīng)用系統(tǒng)中引入 ORO 類(lèi)庫(kù)。
<bean id=”queryPointcutAdvisor”
?????? Class=”org.springframework.aop.support.RegExPointcutAdvisor”>
?????? <property name=”pattern”>
????????????? <value>.*get.+By.+</value>
?????? </property>
?????? <property name=”advice”>
????????????? <ref bean=”queryInterceptor”/>
?????? </property>
</bean>
使用動(dòng)態(tài)切入點(diǎn)
Spring 提供了一種內(nèi)置的動(dòng)態(tài)切入點(diǎn): ControlFlowPointcut 。這個(gè)切入點(diǎn)根據(jù)線(xiàn)程調(diào)用堆棧的信息來(lái)匹配方法。也就是說(shuō),它可以配置成只有當(dāng)指定方法或類(lèi)能在當(dāng)前線(xiàn)程執(zhí)行堆棧中找到時(shí),返回 true 。
<bean id=”servletPointcut” class=”org.springframework.aop.support.ControlFlowPointcut”>
?????? <construct-arg>
????????????? <value>javax.servlet.http.HttpServlet</value>
?????? </construct-arg>
</bean>
<bean id=”servletAdvisor” class=”org.springframework.aop.support.DefaultPointcutAdvisor”>
?????? <property name=”advice”>
????????????? <ref bean=”servletInterceptor”/>
?????? </property>
?????? <property name=”pointcut”>
????????????? <ref bean=”servletPointcut”/>
?????? </property>
</bean>
注: ControlFlowPointcut 明顯比其他的動(dòng)態(tài)切入點(diǎn)慢。
切入點(diǎn)實(shí)施
Spring 支持在切入點(diǎn)上進(jìn)行操作 — 合并與產(chǎn)叉 — 來(lái)創(chuàng)建新的切入點(diǎn)。只有當(dāng)切入點(diǎn)都匹配時(shí)交叉集合才匹配,任何一個(gè)切入點(diǎn)匹配都會(huì)使合并集合匹配。 Spring 為創(chuàng)建這兩種切入點(diǎn)提供了兩個(gè)類(lèi):第一個(gè)類(lèi)是 ComposablePointcut 。通過(guò)將已有的 ComposablePointcut 、切入點(diǎn)、 MethodMatcher 以及 ClassFilter 對(duì)象進(jìn)行合并或交叉,組裝成一個(gè)新的 ComposablePointcut 對(duì)象。這可以通過(guò)調(diào)用 ComposablePointcut 實(shí)例的 intersection () 或 union () 方法實(shí)現(xiàn)。
ComposablePointcut cp=new ComposablePoint()
cp=p.intersection(myPointcut).union(myMethodmatcher);
為了對(duì)兩個(gè) Pointcut 對(duì)象進(jìn)行合并,必須使用 Pointcuts 類(lèi)。這是一個(gè)工具類(lèi),它擁有很多操作 Pointcut 對(duì)象的靜態(tài)方法。如:
Pointcut union=Pointcuts.union(pointcut1,pointcut2);
創(chuàng)建引入
引入與其他類(lèi)型的 Spring 通知有所不同。其它類(lèi)型通知是在方法調(diào)用的周?chē)椚氲讲煌倪B接點(diǎn),而引入則是影響整個(gè)類(lèi),他們通過(guò)給需要消息的類(lèi)型添加方法和屬性來(lái)實(shí)現(xiàn)。也就是說(shuō),你可以用一個(gè)已存在的類(lèi)讓它實(shí)現(xiàn)另外的接口,維持另外的狀態(tài) ( 這也叫混合 ) 。換句話(huà)說(shuō),引入讓你能夠動(dòng)態(tài)地建立復(fù)合對(duì)象,提供了多態(tài)繼承的好處。
實(shí)現(xiàn)
IntroductionInterceptor
Spring 通過(guò)一個(gè)特殊的方法攔截器接口 IntroductionMethodInterceptor 來(lái)實(shí)現(xiàn)引入。這個(gè)接口有一個(gè)方法:
Boolean implementsInterface(Class intf);
如果 IntroductionMethodInterceptor 是為了實(shí)現(xiàn)指定接口,那么方法 implementsInterface 應(yīng)該返回 true 。就是說(shuō),對(duì)用這個(gè)接口聲明的方法的任何調(diào)用將被委托給 IntroductionMethodInterceptor 的 invoke() 方法。 Invoke() 方法負(fù)責(zé)實(shí)現(xiàn)這個(gè)方法,不能調(diào)用 MethodInvocation.proceed() 。它引入了新的接口,調(diào)用目標(biāo)對(duì)象是沒(méi)有用的。
使用
ProxyBeanFactory
BeanFactory 對(duì)象是一個(gè)負(fù)責(zé)創(chuàng)建其他 JavaBean 的 JavaBean 。屬性列表如下:
屬性 |
使? 用 |
target |
代理的目標(biāo)對(duì)象 |
proxyInterfaces |
代理應(yīng)該實(shí)現(xiàn)的接口列表 |
interceptorNames |
需要應(yīng)用到目標(biāo)對(duì)象上的通知 Bean 的名字。可以是攔截器, Advisor 或其他通知類(lèi)型的名字。這個(gè)屬性必須按照在 BeanFactory 中使用的順序設(shè)置。 |
singleton |
是否返回的是同一個(gè)代理實(shí)例。如果使用的是狀態(tài)通知,應(yīng)該設(shè)置為 false |
aopProxyFactory |
使用的 ProxyFactoryBean 實(shí)現(xiàn) |
exposeProxy |
目標(biāo)對(duì)象是否需要得到當(dāng)前的代理。通過(guò)調(diào)用 AopContext.getCurrentProxy 實(shí)現(xiàn)。記住這樣做會(huì)在你的代碼中引入 Spring 專(zhuān)有的 AOP 代碼,所以,盡量避免使用。 |
frozen |
一旦工廠(chǎng)被創(chuàng)建,是否可以修改代理的通知。 |
optimize |
是否對(duì)創(chuàng)建的代理進(jìn)行優(yōu)化 ( 僅適用于 CGLIB) 。 |
ProxyTargetClass |
是否代理目標(biāo)類(lèi),而不是實(shí)現(xiàn)接口。 ( 需要 CGLIB 支持 ) |
大多數(shù)情況下我們只用到前三個(gè)屬性。
如果想避免將目標(biāo)對(duì)象暴露給系統(tǒng)中其他 Bean 的話(huà),可以將它聲明為一個(gè)內(nèi)部 Bean 。
proxyInterfaces 屬性指定了從工廠(chǎng)中創(chuàng)建的 Bean 需要實(shí)現(xiàn)的接口。如:
<property name=”proxyInterfaces”>
?????? <value>com.springinaction.service.CourseService</value>
</property>
這樣就讓 ProxyBeanFactory 知道它創(chuàng)建的所有 Bean 都要實(shí)現(xiàn) CourseService 接口。可以像這樣只提供一個(gè)接口,也可以用 <list> 提供多個(gè)接口。
interceptorNames 屬性定義了一個(gè)應(yīng)用到目標(biāo)對(duì)象上的 Advisor 或通知 Bean 的列表。目標(biāo) Bean 也可以放在此屬性的 <list> 列表的最后,但是最好還是用 Target 屬性設(shè)置目標(biāo) Bean 。
自動(dòng)代理
Spring 有一個(gè)自動(dòng)代理機(jī)制,它可以讓容器為我們產(chǎn)生代理。 Spring 有兩個(gè)類(lèi)提供這種服務(wù): BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator 。
BeanNameAutoProxyCreator 為匹配一系列名字的 Bean 自動(dòng)創(chuàng)建代理。它允許在名字的兩端進(jìn)行通配符匹配。通常用于為符合相同命名規(guī)則的 Bean 應(yīng)用一個(gè)或一組切面 。 ( 主要是解決 target 屬性配置時(shí)目標(biāo)類(lèi)過(guò)多的問(wèn)題。 ) 如:
<bean id=”preformanceThresholdProxyCreator
Class=”org.springframework.aop.framework.autoproxy.BeanNameAutoProxyProlyCreator”>
<property name=”beanNames”>
?????? <list>
????????????? <value>*Service</value>
?????? </list>
</property>
<propery name=”interceptorNames”>
?????? <value>performaceThresholdInterceptor</value>
</property>
</bean>
如果 Bean 是一個(gè) Advisor 或攔截器,它將應(yīng)用到代理對(duì)象的所有方法上。如果是通知的話(huà), Advisor 切入點(diǎn)會(huì)根據(jù)不同 Bean 將通知應(yīng)用到不同的地方。
自動(dòng)代理框架對(duì)于代理需要暴露哪些接口作了一些假設(shè)。目標(biāo)對(duì)象實(shí)現(xiàn)的任何接口代理對(duì)象都要暴露出來(lái)。如果目標(biāo)類(lèi)沒(méi)有實(shí)現(xiàn)任何接口,那么應(yīng)用于前面討論過(guò)的 ProxyFactoryBean 一樣的規(guī)則 — 動(dòng)態(tài)生成一個(gè)子類(lèi)。
DefaultAdvisorAutoProxyCreator
這個(gè)類(lèi)的奇妙之處在于它實(shí)現(xiàn)了 BeanPostProcessor 接口。當(dāng) ApplicationContext 讀入所有 Bean 的配置信息后, DefaultAdvisorAutoProxyCreator 將掃描上下文,尋找所有的 Advisor 。它將這些 Advisor 應(yīng)用到所有符合 Advisor 切入點(diǎn)的 Bean 中。這個(gè)代理創(chuàng)建器只能與 Advisor 配合使用 。 ( 我們知道,一個(gè) Advisor 是一個(gè)切入點(diǎn)和一個(gè)通知的結(jié)合體。 DefaultAdvisorAutoProxyCreator 需要 Advisor 來(lái)知道哪些 Bean 需要通知。 )
<bean id=”advisor” class=”org.springframework.aop.support.RegexpMethodPointcutAdvisor”>
?????? <property name=”advice”>
????????????? <bean class=”performaceThresholdInterceptor”/>
?????? </property>
?????? <property name=”pattern”>
????????????? <value>.+Service\..+</value>
?????? </property>
</bean>
<bean id=”autoProxyCreator”
class=”org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator”/>
在讀入所有 Bean 的定義信息后, BeanFactory 中的所有 Advisor 將被釋放出來(lái)。它們會(huì)將它們的通知配置到任何匹配它們的切入點(diǎn)的 Bean 上 ( 這就是它真正的威力 ) 。
不用顯示地將 Advisor 與其他東西結(jié)合,現(xiàn)在只要簡(jiǎn)單地定義它們,然后讓它們自動(dòng)地應(yīng)用到它們匹配的地方。這樣松耦合 Bean 以及它們的通知就實(shí)現(xiàn)了。
Spring 也支持通過(guò)元數(shù)據(jù) (Metadata) 驅(qū)動(dòng)自動(dòng)代理。這種類(lèi)型自動(dòng)代理,代理配置是通過(guò)源代碼屬性而不是外部配置文件獲得的。最常見(jiàn)的元數(shù)據(jù)自動(dòng)配置是聲明式事務(wù)支持。