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