Spring AOP實(shí)現(xiàn)原理
- 動(dòng)態(tài)代理: 利用核心類
Proxy
和接口InvocationHandler
(基于代理模式的思想) - 字節(jié)碼生成: 利用
CGLIB
動(dòng)態(tài)字節(jié)碼庫
Spring AOP中的關(guān)鍵字
1.Joinpoint
只支持方法執(zhí)行類型的Joinpoint,力求付出20%的努力來滿足80%的開發(fā)需求
2.Pointcut
Spring AOP Pointcut 框架結(jié)構(gòu):
根接口: org.springframework.aop.Pointcut
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
其中晒来,ClassFilter
和MethodMatcher
分別用于匹配將執(zhí)行織入操作的對(duì)象及其相應(yīng)的方法肛度。
如果直接獲取類型為Pointcut
的TRUE
對(duì)象時(shí)羡微,那么代表Pointcut的匹配將會(huì)針對(duì)系統(tǒng)所有的目標(biāo)類以及它們的實(shí)例進(jìn)行谷饿。
MethodMatcher
是一個(gè)重要的接口,其內(nèi)部結(jié)構(gòu)如下:
public interface MethodMatcher {
/**
* Perform static checking whether the given method matches. If this
* returns {@code false} or if the {@link #isRuntime()} method
* returns {@code false}, no runtime check (i.e. no.
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made.
* @param method the candidate method
* @param targetClass the target class (may be {@code null}, in which case
* the candidate class must be taken to be the method's declaring class)
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class<?> targetClass);
/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
* runtime even if the 2-arg matches method returns {@code true}?
* <p>Can be invoked when an AOP proxy is created, and need not be invoked
* again before each method invocation,
* @return whether or not a runtime match via the 3-arg
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
* is required if static matching passed
*/
boolean isRuntime();
/**
* Check whether there a runtime (dynamic) match for this method,
* which must have matched statically.
* <p>This method is invoked only if the 2-arg matches method returns
* {@code true} for the given method and target class, and if the
* {@link #isRuntime()} method returns {@code true}. Invoked
* immediately before potential running of the advice, after any
* advice earlier in the advice chain has run.
* @param method the candidate method
* @param targetClass the target class (may be {@code null}, in which case
* the candidate class must be taken to be the method's declaring class)
* @param args arguments to the method
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class<?> targetClass, Object[] args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
方法
boolean matches(Method method, Class<?> targetClass
表示不會(huì)對(duì)被代理對(duì)象的方法參數(shù)進(jìn)行捕獲妈倔,處理博投。當(dāng)方法isRuntime()
返回false
時(shí),表示執(zhí)行的是該方法盯蝴。這種類型的處理稱之為StaticMethodMathcer
毅哗,可以對(duì)匹配到的結(jié)果進(jìn)行緩存,所以處理性能很高捧挺。
同理虑绵,當(dāng)isRuntime()
返回true
時(shí),代表執(zhí)行的是方法
boolean matches(Method method, Class<?> targetClass, Object[] args)
表示要對(duì)被代理對(duì)象的方法參數(shù)進(jìn)行捕獲闽烙,處理翅睛。這種類型的處理稱之為DynamicMethodMatcher
,因?yàn)閰?shù)不定黑竞,無法進(jìn)行緩存捕发,所以性能不高。
常見的幾種Pointcut
實(shí)現(xiàn):
1.NameMatchMethodPointcut
:
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("test1");
pointcut.setMappedNames(new String[]{"test1","test2"]};
該類可以直接根據(jù)給定的方法名進(jìn)行匹配(也支持簡單的模糊匹配)很魂,不過通過上邊的代碼也可以了解到扎酷,該匹配不涉及目標(biāo)方法的參數(shù),所以對(duì)于重載
方法則無法進(jìn)行有效的支持遏匆。
2.AbstractRegexpMethodPointcut
:
該類支持正則表達(dá)式
來匹配方法法挨,其下有一個(gè)重要的實(shí)現(xiàn)類:JdkRegexpMethodPointcut
。代碼片段如下:
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{".*match.*",".*matches"});
3.AnnotationMatchingPointcut
:
基于注解的方法匹配幅聘。代碼片段如下:
定義自定義注解:
//類級(jí)別的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface ClassLevelAnnotation {
}
//方法級(jí)別的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {
}
將注解應(yīng)用在某一個(gè)類上:
@ClassLevelAnnotation
public class GenericTargetObject {
@MethodLevelAnnotation
public void hello() {
System.out.println("hello");
}
}
利用AnnotationMatchingPointcut
來進(jìn)行匹配:
類級(jí)別的匹配:
AnnotationMatchingPointcut pointcutByClass = new AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
方法級(jí)別的匹配:
AnnotationMatchingPointcut pointcutByMethod = new AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);
或者兩個(gè)結(jié)合使用:
AnnotationMatchingPointcut pointcutByClassAndMethod = new AnnotationMatchingPointcut.forMethodAnnotation(ClassLevelAnnotation.class, MethodLevelAnnotation.class);
4.擴(kuò)展Pointcut:
通過繼承抽象類StaticMethodMatcherPointcut
和DynamicMethodMatcherPointcut
來自定義自己的Pointcut坷剧。
3.Advice
Spring Advice可以分為兩大類:per-class
和 per-instance
。
1.per-class
是指該類型的Advice的實(shí)例可以在目標(biāo)對(duì)象類的所有實(shí)例之間共享喊暖,這種類型的Advice通常只是提供方法攔截的功能,不會(huì)為目標(biāo)對(duì)象類保存任何狀態(tài)或者添加新的特性撕瞧。
其包括:
- Before Advice
代碼片段如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object object) throws Throwable;
}
BeforeAdvice
接口為一個(gè)標(biāo)記接口陵叽,需要繼承該接口,實(shí)現(xiàn)自己的before
邏輯
- ThrowsAdvice
代碼片段如下:
public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Throwable t) {
//普通異常處理
}
public void afterThrowing(RuntimeException e) {
//運(yùn)行時(shí)異常處理
}
}
ThrowsAdvice
同樣也為一個(gè)標(biāo)記接口丛版,不過在實(shí)現(xiàn)它時(shí)巩掺,方法定義需要遵循如下規(guī)則:
void afterThrowing([Method, args, target], ThrowableSubclass);
其中,[]中的可以省略页畦。
- AfterReturningAdvice
其接口定義如下:
public interface AfterReturningAdvice extends AfterAdvice {
/**
* Callback after a given method successfully returned.
* @param returnValue the value returned by the method, if any
* @param method method being invoked
* @param args arguments to the method
* @param target target of the method invocation. May be {@code null}.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
通過該接口方法可以獲取到原方法成功執(zhí)行之后的返回值胖替,不過不可以對(duì)其進(jìn)行修改。
- Around Advice
Spring沒有提供該接口的規(guī)范,而是直接使用了AOP Alliance的標(biāo)準(zhǔn)接口独令,如下:
pubic interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
能夠cover
前面幾種類型的Advice
端朵,很強(qiáng)大!
2.per-instance
類型的Advice不會(huì)在目標(biāo)類所有對(duì)象實(shí)例之間共享燃箭,而是會(huì)為不同的實(shí)例對(duì)象保存它們各自的狀態(tài)以及相關(guān)邏輯冲呢。它織入面向的是對(duì)象
。在Spring AOP中招狸,Introduction
就是唯一的一種per-instance
型的Advice
敬拓,對(duì)其進(jìn)行實(shí)現(xiàn)的是IntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}
其IntroductionInterceptor
接口還繼承了兩個(gè)重要的接口MethodInterceptor
和DynamicIntroductionAdvice
。其中接口DynamicIntroductionAdvice
界定為哪些接口類提供相應(yīng)的攔截功能裙戏,通過接口MethodInterceptor
來處理新添加的接口方法調(diào)用乘凸。
如果要添加切入的邏輯操作,可以直接擴(kuò)展IntroductionInterceptor
中的invoke
方法累榜,不過也可以利用Spring提供的兩個(gè)實(shí)現(xiàn)類來完成:
- DelegatingIntroductionInterceptor (需要設(shè)置其
scope
=prototype
)
來簡單看一段偽代碼演示:
{SubClass} targetObject = new {Class()};
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegate);
//進(jìn)行織入
{SubClass} proxyObject = {SubClass} weaver.weave(interceptor).getPxory();
- DelegatePerTargetObjectIntroductionInterceptor
4.Aspect
Aspect在Spring中表示為Advisor
营勤,其體系結(jié)構(gòu)分為兩類:PointcutAdvisor
和IntroductionAdvisor
1.PointcutAdvisor
接口:
public interface PointcutAdvisor extends Advisor {
/**
* Get the Pointcut that drives this advisor.
*/
Pointcut getPointcut();
}
對(duì)該接口的實(shí)現(xiàn)有幾個(gè)比較重要的類:DefaultPointcutAdvisor
、NameMatchMethodPointcutAdvisor
和RegexpMethodPointcutAdvisor
來看一下DefaultPointcutAdvisor
的構(gòu)造方法:
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
可以看出信柿,該類是持有一個(gè)Pointcut
和Advice
冀偶,所以從這一點(diǎn)上也可以將Spring的Advisor理解為封裝了Pointcut
和Advice
的一個(gè)容器。
然后對(duì)于類NameMatchMethodPointcutAdvisor
和RegexpMethodPointcutAdvisor
渔嚷,聯(lián)想一下前邊了解到的幾種類型的Pointcut进鸠,不難得出,這兩個(gè)類對(duì)于Pointcut的封裝則分別為NameMatchMethodPointcut
和RegexpMethodPointcut
形病。
2.IntroductionAdvisor
接口:是對(duì)Introduction
類型的封裝客年,其內(nèi)部持有的Pointcut和Advice只能是都是Introduction
類型的。
5.Ordered
可以通過Ordered
接口來為不同的Advisor
配置相應(yīng)的優(yōu)先級(jí)漠吻。
在xml中來為Advisor
指定不同的優(yōu)先級(jí):
<bean id="pemissionAuthAdvisor" class="xxx">
<property name="order" value="1">
<bean id="exceptionBarrierAdvisor" class="yyy">
<property name="order" value="0">
當(dāng)然也可以在代碼中量瓜,通過調(diào)用抽象類AbstractPointcutAdvisor
的
public void setOrder(int order) {
this.order = order;
}
來為不同的Advisor設(shè)置相應(yīng)的優(yōu)先級(jí)
Spring AOP 織入器
上邊的流程全部走完一遍之后,接下來就是如何調(diào)用Spring AOP提供的接口來獲得針對(duì)目標(biāo)對(duì)象的一個(gè)代理對(duì)象啦途乃。有兩種方法绍傲,分別是ProxyFactory
和ProxyFactoryBean
- ProxyFactory
簡單看一下ProxyFactory
的代碼演示:
ProxyFactory weaver = new ProxyFactory();
Advisor advisor = ..;
waever.addAdvisor(advisor);
weaver.setTarget({yourTargetObject});
Object proxyObject = weaver.getProxy();
所以如果要利用ProxyFactory
來生成代理對(duì)象,需要設(shè)置兩處:目標(biāo)對(duì)象:yourTargetObject
和Advisor
耍共。
上邊說到Sping AOP的實(shí)現(xiàn)包括動(dòng)態(tài)代理
和字節(jié)碼
生成烫饼,那么什么時(shí)候采用其中的某種方式呢?
對(duì)于動(dòng)態(tài)代理
如果Spring AOP檢測到targetObject
實(shí)現(xiàn)了相應(yīng)的接口试读,那么就會(huì)采用動(dòng)態(tài)代理杠纵。可以顯示指定接口钩骇,比如:
weaver.setInterfaces();
也可以不用指定比藻,框架會(huì)自動(dòng)檢測铝量。
但是也可以 通過設(shè)置相應(yīng)的屬性來強(qiáng)制轉(zhuǎn)換成字節(jié)碼生成方式,方式為:
ProxyFactory weaver = new ProxyFactory();
weaver.setproxyTargetClass(true);
另外還有兩種方式是要采用字節(jié)碼生成方式來代理的:
1.如果targetObject沒有實(shí)現(xiàn)任何接口
2.將ProxyFactory的optimize
屬性置為true
以前總結(jié)的都是針對(duì)類級(jí)別银亲,即per-class
的代理慢叨,而對(duì)于per-instance
級(jí)別的代理該如何去做呢?
來簡單看一段偽代碼演示吧:
ProxyFactory weaver = new ProxyFactory();
/*
*面向接口的代理
*weaver.setProxyTargetClass(true);面向類的代理群凶,即CGLIB代理
*/
weaver.setInterfaces(new class[] {Interface1.class, Interface2.class});
DemoIntroductionInterceptor advice = new DemoIntroductionInterceptor();
weaver.addAdvice(advice);
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice, advice);
weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
- ProxyFactoryBean
ProxyFactoryBean
為IOC容器中的一個(gè)織入器插爹。同proxyFactory
,它也支持基于接口和基于類的代理请梢。
參考書籍:
1.《Spring揭秘》