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