Spring AOP(一)

Spring AOP實(shí)現(xiàn)原理

  • 動(dòng)態(tài)代理: 利用核心類Proxy和接口InvocationHandler(基于代理模式的思想)
  • 字節(jié)碼生成: 利用CGLIB動(dòng)態(tài)字節(jié)碼庫(kù)

Spring AOP中的關(guān)鍵字

1.Joinpoint

只支持方法執(zhí)行類型的Joinpoint,力求付出20%的努力來(lái)滿足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;

}

其中,ClassFilterMethodMatcher分別用于匹配將執(zhí)行織入操作的對(duì)象及其相應(yīng)的方法。

如果直接獲取類型為PointcutTRUE對(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ù)不定,無(wú)法進(jìn)行緩存,所以性能不高。

常見的幾種Pointcut實(shí)現(xiàn):

1.NameMatchMethodPointcut:

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("test1");
pointcut.setMappedNames(new String[]{"test1","test2"]};

該類可以直接根據(jù)給定的方法名進(jìn)行匹配(也支持簡(jiǎn)單的模糊匹配),不過通過上邊的代碼也可以了解到,該匹配不涉及目標(biāo)方法的參數(shù),所以對(duì)于重載方法則無(wú)法進(jìn)行有效的支持。

2.AbstractRegexpMethodPointcut:

該類支持正則表達(dá)式來(lái)匹配方法,其下有一個(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來(lái)進(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:

通過繼承抽象類StaticMethodMatcherPointcutDynamicMethodMatcherPointcut來(lái)自定義自己的Pointcut。

3.Advice

Spring Advice可以分為兩大類:per-classper-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è)重要的接口MethodInterceptorDynamicIntroductionAdvice。其中接口DynamicIntroductionAdvice界定為哪些接口類提供相應(yīng)的攔截功能,通過接口MethodInterceptor來(lái)處理新添加的接口方法調(diào)用。

如果要添加切入的邏輯操作,可以直接擴(kuò)展IntroductionInterceptor中的invoke方法,不過也可以利用Spring提供的兩個(gè)實(shí)現(xiàn)類來(lái)完成:

  • DelegatingIntroductionInterceptor (需要設(shè)置其scope = prototype)

來(lái)簡(jiǎn)單看一段偽代碼演示:

{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)分為兩類:PointcutAdvisorIntroductionAdvisor

1.PointcutAdvisor接口:

public interface PointcutAdvisor extends Advisor {

    /**
     * Get the Pointcut that drives this advisor.
     */
    Pointcut getPointcut();

}

對(duì)該接口的實(shí)現(xiàn)有幾個(gè)比較重要的類:DefaultPointcutAdvisor、NameMatchMethodPointcutAdvisorRegexpMethodPointcutAdvisor

來(lái)看一下DefaultPointcutAdvisor的構(gòu)造方法:

    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
        this.pointcut = pointcut;
        setAdvice(advice);
    }

可以看出,該類是持有一個(gè)PointcutAdvice,所以從這一點(diǎn)上也可以將Spring的Advisor理解為封裝了PointcutAdvice的一個(gè)容器。

然后對(duì)于類NameMatchMethodPointcutAdvisorRegexpMethodPointcutAdvisor,聯(lián)想一下前邊了解到的幾種類型的Pointcut,不難得出,這兩個(gè)類對(duì)于Pointcut的封裝則分別為NameMatchMethodPointcutRegexpMethodPointcut。

2.IntroductionAdvisor接口:是對(duì)Introduction類型的封裝,其內(nèi)部持有的Pointcut和Advice只能是都是Introduction類型的。

5.Ordered

可以通過Ordered接口來(lái)為不同的Advisor配置相應(yīng)的優(yōu)先級(jí)。

在xml中來(lái)為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;
    }

來(lái)為不同的Advisor設(shè)置相應(yīng)的優(yōu)先級(jí)

Spring AOP 織入器

上邊的流程全部走完一遍之后,接下來(lái)就是如何調(diào)用Spring AOP提供的接口來(lái)獲得針對(duì)目標(biāo)對(duì)象的一個(gè)代理對(duì)象啦。有兩種方法,分別是ProxyFactoryProxyFactoryBean

  • ProxyFactory

簡(jiǎn)單看一下ProxyFactory的代碼演示:

ProxyFactory weaver = new ProxyFactory();
Advisor advisor = ..;
waever.addAdvisor(advisor);
weaver.setTarget({yourTargetObject});
Object proxyObject = weaver.getProxy();

所以如果要利用ProxyFactory來(lái)生成代理對(duì)象,需要設(shè)置兩處:目標(biāo)對(duì)象:yourTargetObjectAdvisor。

上邊說到Sping AOP的實(shí)現(xiàn)包括動(dòng)態(tài)代理字節(jié)碼生成,那么什么時(shí)候采用其中的某種方式呢?

對(duì)于動(dòng)態(tài)代理如果Spring AOP檢測(cè)到targetObject實(shí)現(xiàn)了相應(yīng)的接口,那么就會(huì)采用動(dòng)態(tài)代理。可以顯示指定接口,比如:

weaver.setInterfaces();

也可以不用指定,框架會(huì)自動(dòng)檢測(cè)。

但是也可以 通過設(shè)置相應(yīng)的屬性來(lái)強(qiáng)制轉(zhuǎn)換成字節(jié)碼生成方式,方式為:

ProxyFactory weaver = new ProxyFactory();
weaver.setproxyTargetClass(true);

另外還有兩種方式是要采用字節(jié)碼生成方式來(lái)代理的:

1.如果targetObject沒有實(shí)現(xiàn)任何接口

2.將ProxyFactory的optimize屬性置為true

以前總結(jié)的都是針對(duì)類級(jí)別,即per-class的代理,而對(duì)于per-instance級(jí)別的代理該如何去做呢?

來(lái)簡(jiǎn)單看一段偽代碼演示吧:

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揭秘》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容