011-Spring aop 002-核心說明-切點PointCut、通知Advice、切面Advisor


一、概述

  切點Pointcut,切點代表了一個關於目標函數的過濾規則,后續的通知是基於切點來跟目標函數關聯起來的。

  然后要圍繞該切點定義一系列的通知Advice,如@Before、@After、@AfterReturning、@AfterThrowing、@Around等等定義的方法都是通知。其含義是在切點定義的函數執行之前、完成之后、正常返回之后、拋出異常之后以及環繞前后執行對應的切面邏輯。

  一個切點和針對該切點的一個通知共同構成了一個切面Advisor。對於一個方法,我們可以定義多個切點都隱含它,並且對於每個切點都可定義多個通知來形成多個切面,SpringAOP底層框架會保證在該方法調用時候將所有符合條件的切面都切入到其執行之前或之后或環繞。通知Advice的子類Interceptor或MethodInterceptor的類名更具體一些,包含了攔截器的概念。

  SpringAOP使用運行時連接點Joinpoint的概念將切面切入到調用方法中,一個運行時連接點就是對於一個可訪問對象的訪問過程的具體化,可能其子類Invocation或MethodInvocation的類名會更加具體一些。在實際調用中運行時連接點包括了被調用方法、被調用對象、適用於該方法的攔截器鏈等等信息。

  執行的過程類似於FilterChain,先正向執行攔截器鏈的前置邏輯,然后調用method,接着反向執行攔截器鏈的后置邏輯,最后返回結果。

1.1、切點PointCut

上例中

    @Pointcut("execution(public * com.github.bjlhx15.springaop.service.MyTestService.doSomething1*(..))")
    public void doSomethingPointcut(){};

    @Pointcut("@annotation(com.github.bjlhx15.springaop.anno.TestTimer)")
    public void timerPointcut(){};

    @Pointcut("@within(com.github.bjlhx15.springaop.anno.TestLogger)")
    public void recordLogPointcut(){};

  都是用於定義一個切點,注釋Pointcut中的value值就是切入點指示符,SpringAOP提供的這種匹配表達式是用於計算哪些方法符合該切點的定義。Pointcut接口如下所示:

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
    Pointcut TRUE = TruePointcut.INSTANCE;
}

  其中定義了兩個抽象方法:獲得類過濾器和獲得方法匹配器。意思很明確,就是可以通過類過濾及方法過濾,來定義對目標函數的過濾規則。各子類可以指定具體的過濾器來實現不同的過濾過則。

  Spring2.0中增加了AspectJExpressionPointcut來支持AspectJ關於切點定義的表達式語法。其中定義了支持的各種類型的切點函數,並支持通配符和邏輯表達式。

1.1.1、原生切點函數

  原生切點函數就是我們在示例中定義切點時使用的execution、@annotation、@within等函數,在AspectJExpressionPointcut中定義了支持的各種類型的原生切點函數:

private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

其中:PointcutPrimitive 是來自於aspectj 的 參數。

常用的原生切點函數:

1、類型原生切點函數within

針對類型(全限定名)的過濾方法,語法格式如下:within(<typeName>);typeName表示類或接口的全限定名,支持使用通配符,例如:

/**
*匹配aopnew.service包中所有以MyTestService開頭的類中的所有方法
*/
@Pointcut("within(aopnew.service.MyTestService*)")
 
/**
*匹配所有實現anpnew.interface.IUserService接口的類的所有方法
*/
@Pointcut("within(anpnew.interface.IUserService+)")

2、方法原生切點函數execution

針對方法簽名進行過濾,語法表達式如下:

/**
*scope:表示方法作用域,例如:public, private, protect
*return-type:表示返回類型
*fully-qualified-class-name:表示類的完全限定名
*method-name:表示方法名
*parameters:表示參數
*/
execution(<scope> <return-type> <fully-qualified-class-name><method-name>(<parameters>))

對於給定的作用域、返回值類型、完全限定類名、方法名以及參數匹配的方法將會應用切點函數指定的通知,支持使用通配符,例如:

/**
*匹配作用域為public,所在類全限定名為aopnew.service.MyTestService,方法名以doSomething開頭的所有方法
*
*/
@Pointcut("execution(public * aopnew.service.MyTestService.doSomething*(..))")

3、類注釋原生切點函數@within

用於匹配標注了指定注釋的類型內的所有方法,與within是有區別的,within是用於匹配指定類型內的方法執行;語法如下:@within(<annotationName>)

annotationName表示注釋類的全限定名,支持使用通配符,例如:

/**
*匹配標注了TestLogger的類中的所有方法
*/
@Pointcut("@within(aopnew.annotation.TestLogger)")

4、方法注釋原生切點函數@annotation

用於匹配所有標注了指定注解的方法,語法如下:@annotation(<annotationName>)

annotationName表示注釋類的全限定名,支持使用通配符,例如:

/** *匹配所有標注了TestTimer的方法 */ @Pointcut("@annotation(aopnew.annotation.TestTimer)")

1.1.2、通配符

上述的原生切點函數中都支持通配符,在示例中我們看到了很多如  *  ,  ..  , +等,它們的含義如下:

.. :匹配方法定義中的任意數量的參數,此外還匹配類定義中的任意數量包,例如:

/**
*匹配aopnew包及子包中的類名為MyTestService2中的以doSomething開頭並且作用域為public的所有方法
*/
@Pointcut("execution(public * aopnew..MyTestService2.doSomething*(..))")

+ :匹配給定類的任意子類,例如:

/**
*匹配所有實現anpnew.interface.IUserService接口的類的所有方法
*/
@Pointcut("within(anpnew.interface.IUserService+)")

* :匹配任意數量的字符,例如:

/**
*匹配aopnew.service包中任意類中的所有方法
*/
@Pointcut("within(aopnew.service.*)")

1.1.3、邏輯表達式

切點指示符可以使用運算符語法進行表達式的混編,如and、or、not(或&&、||、!),例如:

/**
*匹配類上標注了TestLogger並且方法上標注了TestTimer的所有方法
*/
@Pointcut("@within(aopnew.annotation.TestLogger) && @annotation(aopnew.annotation.TestTimer)")

1.2、通知Advice

  通知Advice描述了當符合某切點的方法調用時,在調用過程的哪個時機執行哪樣的切面邏輯。Spring2.0引入了AspectJ的通知類型,主要分5種,分別是前置通知@Before、后置通知@AfterReturn、異常通知@AfterThrowing、最終通知@After以及環繞通知@Around。

  單單解釋通知Advice可能不是很直觀,其子類攔截器Interceptor可能更直觀更容易理解一些。AspectJ各個不同的通知注釋最終會解析並構建成為不同類型的攔截器,它們的作用就是攔截方法並在方法調用的不同時機執行攔截器定義的切入邏輯。

1、前置通知@Before

  前置通知通過@Before注解進行標注,可直接傳入切點表達式的值也可以傳入@Pointcut定義的切點函數名。該通知在目標函數執行前執行,其中傳遞的參數JoinPoint是運行時對方法調用過程的一個具體化,是一個運行時動態的概念,內部包含了被調用方法、方法所在的對象及攔截器鏈等信息。

2、后置通知@AfterReturning 

  通過@AfterReturning注解進行標注,該函數在目標函數執行完成后執行,並可以獲取到目標函數最終的返回值returnVal,當目標函數沒有返回值時,returnVal將返回null,必須通過returning = “returnVal”注明參數的名稱而且必須與通知函數的參數名稱相同。

  請注意,在任何通知中這些參數都是可選的,需要使用時直接填寫即可,不需要使用時,可以完全不用聲明出來。

3、異常通知 @AfterThrowing

  該通知只有在異常時才會被觸發,並由throwing來聲明一個接收異常信息的變量,同樣異常通知也擁有Joinpoint參數,需要時加上即可

4、最終通知 @After

  該通知有點類似於finally代碼塊,只要應用了無論什么情況下都會執行。

5、環繞通知@Around 

  環繞通知既可以在目標方法前執行也可在目標方法之后執行,更重要的是環繞通知可以控制目標方法是否指向執行。第一個參數必須是ProceedingJoinPoint,通過該對象的proceed()方法來傳遞攔截器(通知)鏈或執行函數,proceed()的返回值就是環繞通知的返回值。

  同樣的,ProceedingJoinPoint是運行時對方法調用過程的一個具體化,是一個運行時動態的概念,內部包含了被調用方法、方法所在的對象及攔截器鏈等信息,並且其相較於JoinPoint增加了proceed函數用於傳遞攔截器鏈或執行函數。

1.2.1、說明

  通知的繼承路徑為:Advice<-Interceptor<-MethodInterceptor

其中MethodInterceptor的接口定義如下:

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

  也就是說方法攔截器的子類都需要實現一個方法,接受MethodInvocation類型的參數invocation。MethodInvocation顧名思義是動態概念方法調用的具體化,其本質上是一個運行時連接點JoinPoint。

  MethodInterceptor子類在invoke方法中執行自己的業務邏輯並調用invocation.proceed()來傳遞攔截器調用鏈。例如:

Object invoke(MethodInvocation invocation) throws Throwable{
    ...do something before method invocation...        
    Object obj = null;
    try{
    obj = invocation.proceed();
    }catch(Throwable e){
        ...do something after throwing...
    }finally{
        ...do something after method invoke...
    }
    ...do something after method return...
    return obj;
}

  上面的示例顯示出了攔截器可以在方法調用的各個時機執行切入業務的大體實現,而前面的五種通知本質上都是上述代碼的一個變種。

1.3、切面Advisor

  當符合某切點條件的函數在被執行時,就產生了一個運行時連接點Joinpoint的概念。運行時連接點代表了一個在靜態連接點(程序中的某個位置)上發生的事件。例如:一次調用就是一個對於方法(靜態連接點)的運行時連接點。

  在基於攔截器框架的上下文中,一個運行時連接點就是對於一個可訪問對象的訪問過程的具體化。Joinpoint接口定義如下:

public interface Joinpoint {
    Object proceed() throws Throwable;
    Object getThis();
    AccessibleObject getStaticPart();
}

  如上所述Joinpoint代表了運行時連接點,也就是代表了方法調用過程的具體化。因此是一個動態的概念,getThis()就是返回這個運行時連接點的動態部分(如方法所在的對象實例),而getStaticPart()就用於返回對應的靜態連接點的信息(如方法定義本身)。

  另外,proceed()用於執行本運行時連接點的攔截器鏈上的下一個攔截器。由此可知,運行時連接點中除了維護被調用方法,方法所在的對象實例外還應該維護定義於該方法的所有攔截器(通知)。Joinpoint接口的繼承鏈為:

Joinpoint<-Invocation<-MethodInvocation<-ProxyMethodInvocation

  從子類的名稱上會更容易理解,運行時連接點更側重的是描述一個調用的過程。其實現類為ReflectiveMethodInvocation,該類中維護的屬性如下:

    protected final Object proxy;
 
 
    protected final Object target;
 
 
    protected final Method method;
 
 
    protected Object[] arguments;
 
 
    private final Class<?> targetClass;
 
 
    /**
     * Lazily initialized map of user-specific attributes for this invocation.
     */
    private Map<String, Object> userAttributes;
 
 
    /**
     * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
     * that need dynamic checks.
     */
    protected final List<?> interceptorsAndDynamicMethodMatchers;
 
 
    /**
     * Index from 0 of the current interceptor we're invoking.
     * -1 until we invoke: then the current interceptor.
     */
    private int currentInterceptorIndex = -1;
View Code

其中interceptorsAndDynamicMethodMatchers就是我們上面所說的攔截器鏈,ReflectiveMethodInvocation的proceed方法如下所示:

    @Override
    public Object proceed() throws Throwable {
        //    We start with an index of -1 and increment early.
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
 
 
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        }
        else {
            // It's an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }
View Code

結合前面對於MethodIntercepter子類中invoke函數的實現,運行時連接點中攔截器鏈的調用方式如下:

  1、如果攔截器鏈尚未執行完,就執行攔截器鏈上的下一個攔截器並將this(本動態連接點)傳遞過去

  2、每一個攔截器的攔截函數中都執行自己的前置邏輯並調用invocation.proceed()重復步驟1

  3、檔攔截器鏈執行完畢,則執行方法調用,並返回結果

  4、在返回的過程中,按照前面調用順序的反向順序執行方法調用的后置邏輯,也就是在invocation.proceed()之后編寫的邏輯

  5、攔截器鏈反向執行完成后,最終返回結果。

由此可得,一個定義了切面的方法調用過程如下所示:

interceptor1.before()
interceptor2.before()
......
interceptorn.before()
method.invoke()
interceptorn.aft()
......
interceptor2.aft()
interceptor1.aft()

  @Before定義的通知(攔截器)只有before()邏輯;@After、@AfterReturning、@AfterThrowing定義的通知(攔截器)只有after()邏輯;@Around定義的通知(攔截器)可以自己來定義before()和after()邏輯。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM