關於Spring AOP,除了動態代理、CGLIB,你還知道什麼?

Spring 做爲 Java 中最流行的框架,主要歸功於其提供的 IOC 和 AOP 功能。本文將討論 Spring AOP 的實現。第一節將介紹 AOP 的相關概念,若熟悉可跳過,第二節中結合源碼介紹 Spring 是如何實現 AOP 的各概念。java

1. AOP 概念

1.1 JoinPoint

進行織入操做的程序執行點。正則表達式

常見類型:spring

  • 方法調用(Method Call):某個方法被調用的時點。數組

  • 方法調用執行(Method Call Execution):某個方法內部開始執行的時點。app

    方法調用是在調用對象上的執行點,方法調用執行是在被調用對象的方法開始執行點。框架

  • 構造方法調用(Constructor Call):對某個對象調用其構造方法的時點。ide

  • 構造方法執行(Constructor Call Execution):某個對象構造方法內部開始執行的時點。模塊化

  • 字段設置(Field Set):某個字段經過 setter 方法被設置或直接被設置的時點。函數

  • 字段獲取(Field Get):某個字段經過 getter 方法被訪問或直接被訪問的時點。學習

  • 異常處理執行(Exception Handler Execution):某些類型異常拋出後,異常處理邏輯執行的時點。

  • 類初始化(Class Initialization):類中某些靜態類型或靜態塊的初始化時點。

1.2 Pointcut

Jointpoint 的表述方式。

常見表述方式:

  • 直接指定 Joinpoint 所在方法名稱
  • 正則表達式
  • 特定的 Pointcut 表述語言

1.3 Advice

單一橫切關注點邏輯的載體,織入到 Joinpoint 的橫切邏輯。

具體形式:

  • Before Advice:Joinpoint 處以前執行。
  • After Advice:Joinpoint 處以後執行,細分爲三種:
    • After Returning Advice:Joinpoint 處正常完成後執行。
    • After Throwing Advice:Joinpoint 處拋出異常後執行。
    • After Finally Advice:Joinpoint 處正常完成或拋出異常後執行。
  • Around Advice:包裹 Joinpoint,在 Joinpoint 以前和以後執行,具備 Before Advice 和 After Advice 的功能。
  • Introduction:爲原有的對象添加新的屬性或行爲。

1.4 Aspect

對橫切關注點邏輯進行模塊化封裝的 AOP 概念實體,包含多個 Pointcut 和相關 Advice 的定義。

1.5 織入和織入器

織入:將 Aspect 模塊化的橫切關注點集成到 OOP 系統中。

織入器:用於完成織入操做。

1.6 Target

在織入過程當中被織入橫切邏輯的對象。

將上述 6 個概念放在一塊,以下圖所示:

AOP各個概念所處的場景

在瞭解 AOP 的各類概念後,下面將介紹 Spring 中 AOP 概念的具體實現。

2. Spring 中的實現

前文提到 AOP 的 Joinpoint 有多種類型,方法調用、方法執行、字段設置、字段獲取等。而在 Spring AOP 中,僅支持方法執行類型的 Joinpoint,但這樣已經能知足 80% 的開發須要,若是有特殊需求,可求助其餘 AOP 產品,如 AspectJ。因爲 Joinpoint 涉及運行時的過程,至關於組裝好全部部件讓 AOP 跑起來的最後一步。因此將介紹完其餘概念實現後,最後介紹 Joinpoint 的實現。

2.1 Pointcut

因爲 Spring AOP 僅支持方法執行類別的 Joinpoint,所以 Pointcut 須要定義被織入的方法,又由於 Java 中方法封裝在類中,因此 Pointcut 須要定義被織入的類和方法,下面看其實現。

Spring 用 org.springframework.aop.Pointcut 接口定義 Pointcut 的頂層抽象。

public interface Pointcut {
    
   // ClassFilter用於匹配被織入的類 
   ClassFilter getClassFilter();

   // MethodMatcher用於匹配被織入的方法
   MethodMatcher getMethodMatcher();

   // TruePoincut的單例對象,默認匹配全部類和方法
   Pointcut TRUE = TruePointcut.INSTANCE;
}
複製代碼

咱們能夠看出,Pointcut 經過 ClassFilterMethodMatcher 的組合來定義相應的 Joinpoint。Pointcut 將類和方法拆開來定義,是爲了可以重用。例若有兩個 Joinpoint,分別是 A 類的 fun() 方法和 B 類的 fun() 方法,兩個方法簽名相同,則只需一個 fun() 方法的 MethodMatcher 對象,達到了重用的目的,ClassFilter 同理。

下面瞭解下 ClassFilterMethodMatcher如何進行匹配

ClassFilter 使用**matches方法**匹配被織入的類,定義以下:

public interface ClassFilter {
    
    // 匹配被織入的類,匹配成功返回true,失敗返回false
    boolean matches(Class<?> clazz);

    // TrueClassFilter的單例對象,默認匹配全部類
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
複製代碼

MethodMatcher 也是使用 matches方法 匹配被織入的方法,定義以下:

public interface MethodMatcher {
    
   // 匹配被織入的方法,匹配成功返回true,失敗返回false
   // 不考慮具體方法參數
   boolean matches(Method method, Class<?> targetClass);
    
   // 匹配被織入的方法,匹配成功返回true,失敗返回false
   // 考慮具體方法參數,對參數進行匹配檢查
   boolean matches(Method method, Class<?> targetClass, Object... args);
   
   // 一個標誌方法
   // false表示不考慮參數,使用第一個matches方法匹配
   // true表示考慮參數,使用第二個matches方法匹配
   boolean isRuntime();

   // TrueMethodMatcher的單例對象,默認匹配全部方法
   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
複製代碼

看到 matches 方法的聲明,你是否會以爲有點奇怪,在 ClassFilter 中不是已經對類進行匹配了嗎,那爲何在 MethodMatchermatches 方法中還有一個 Class<?> targetClass 參數。請注意,這裏的 Class<?> 類型參數將不會進行匹配,而僅是爲了找到具體的方法。例如:

public boolean matches(Method method, Class<?> targetClass) {
    Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    ...
}
複製代碼

MethodMatcher相比ClassFilter特殊在有兩個 matches 方法。將根據 isRuntime() 的返回結果決定調用哪一個。而MethodMatcherisRuntime()分爲兩個抽象類 StaticMethodMatcher(返回false,不考慮參數)和 DynamicMethodMatcher(返回true,考慮參數)。

Pointcut 也因 MethodMathcer 可分爲 StaticMethodMatcherPointcutDynamicMethodMatcherPointcut,相關類圖以下所示:

Pointcut相關類圖

DynamicMethodMatcherPointcut 本文將不介紹,主要介紹下類圖中列出的三個實現類。

(1)NameMatchMethodPointcut

經過指定方法名稱,而後與方法的名稱直接進行匹配,還支持 「*」 通配符。

public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable {
    
    // 方法名稱
    private List<String> mappedNames = new ArrayList<>();

    // 設置方法名稱
    public void setMappedNames(String... mappedNames) {
        this.mappedNames = new ArrayList<>(Arrays.asList(mappedNames));
    }


    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        for (String mappedName : this.mappedNames) {
            // 根據方法名匹配,isMatch提供「*」通配符支持
            if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
                return true;
            }
        }
        return false;
    }
    
    // ...
}
複製代碼

(2)JdkRegexpMethodPointcut

內部有一個 Pattern 數組,經過指定正則表達式,而後和方法名稱進行匹配。

(3)AnnotationMatchingPointcut

根據目標對象是否存在指定類型的註解進行匹配。

2.2 Advice

Advice 爲橫切邏輯的載體,Spring AOP 中關於 Advice 的接口類圖以下所示:

Advice相關類圖

(1)MethodBeforeAdvice

橫切邏輯將在 Joinpoint 方法以前執行。可用於進行資源初始化或準備性工做。

public interface MethodBeforeAdvice extends BeforeAdvice {
    
    void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
    
}
複製代碼

下面來實現一個 MethodBeforeAdvice,看下其效果。

public class PrepareResourceBeforeAdvice implements MethodBeforeAdvice {
    
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("準備資源");
    }
    
}
複製代碼

定義一個 ITask 接口:

public interface ITask {
    
    void execute();
    
}
複製代碼

ITask 的實現類 MockTask

public class MockTask implements ITask {
    
   @Override
   public void execute() {
      System.out.println("開始執行任務");
      System.out.println("任務完成");
   }
    
}
複製代碼

Main 方法以下,ProxyFactoryAdvisor 在後續會進行介紹,先簡單瞭解下,經過ProxyFactory拿到代理類,Advisor用於封裝 PointcutAdvice

public class Main {
    
   public static void main(String[] args) {
      MockTask task = new MockTask();
      ProxyFactory weaver = new ProxyFactory(task);
      weaver.setInterfaces(new Class[]{ITask.class});
      // 內含一個NameMatchMethodPointcut
      NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
      // 指定NameMatchMethodPointcut的方法名
      advisor.setMappedName("execute");
      // 指定Advice
      advisor.setAdvice(new PrepareResourceBeforeAdvice());
      weaver.addAdvisor(advisor);
      ITask proxyObject = (ITask) weaver.getProxy();
      proxyObject.execute();
   }
    
}

/** output: 準備資源 開始執行任務 任務完成 **/
複製代碼

能夠看出在執行代理對象 proxyObjectexecute 方法時,先執行了 PrepareResourceBeforeAdvice 中的 before 方法。

(2)ThrowsAdvice

橫切邏輯將在 Joinpoint 方法拋出異常時執行。可用於進行異常監控工做。

ThrowsAdvice 接口未定義任何方法,但約定在實現該接口時,定義的方法需符合以下規則

void afterThrowing([Method, args, target], ThrowableSubclass) 複製代碼

前三個參數爲 Joinpoint 的相關信息,可省略。ThrowableSubclass 指定須要攔截的異常類型。

例如可定義多個 afterThrowing 方法捕獲異常:

public class ExceptionMonitorThrowsAdvice implements ThrowsAdvice {
    
    public void afterThrowing(Throwable t) {
        System.out.println("發生【普通異常】");
    }
    
    public void afterThrowing(RuntimeException e) {
        System.out.println("發生【運行時異常】");
    }
    
    public void afterThrowing(Method m, Object[] args, Object target, ApplicationException e) {
        System.out.println(target.getClass() + m.getName() + "發生【應用異常】");
    }
    
}
複製代碼

修改下 MockTask 的內容:

public class MockTask implements ITask {
    
    @Override
    public void execute() {
        System.out.println("開始執行任務");
        // 拋出一個自定義的應用異常
        throw new ApplicationException();
        // System.out.println("任務完成");
    }
    
}
複製代碼

修改下 Main 的內容:

public class Main {
    
    public static void main(String[] args) {
        MockTask task = new MockTask();
        ProxyFactory weaver = new ProxyFactory(task);
        weaver.setInterfaces(new Class[]{ITask.class});
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("execute");
        // 指定異常監控Advice
        advisor.setAdvice(new ExceptionMonitorThrowsAdvice());
        weaver.addAdvisor(advisor);
        ITask proxyObject = (ITask) weaver.getProxy();
        proxyObject.execute();
    }
    
}

/** output: 開始執行任務 class com.chaycao.spring.aop.MockTaskexecute發生【應用異常】 **/
複製代碼

當拋出 ApplicationException 時,被相應的 afterThrowing 方法捕獲到。

(3)AfterReturningAdvice

橫切邏輯將在 Joinpoint 方法正常返回時執行。可用於處理資源清理工做。

public interface AfterReturningAdvice extends AfterAdvice {
    
   void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
    
}
複製代碼

實現一個資源清理的 Advice :

public class ResourceCleanAfterReturningAdvice implements AfterReturningAdvice {
    
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
      System.out.println("資源清理");
   }
    
}
複製代碼

修改 MockTask 爲正常執行成功, 修改 Main 方法爲指定 ResourceCLeanAfterReturningAdvice,效果以下:

/** output: 開始執行任務 任務完成 資源清理 **/
複製代碼

(4)MethodInterceptor

至關於 Around Advice,功能十分強大,可在 Joinpoint 方法先後執行,甚至修改返回值。其定義以下:

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

MethodInvocation 是對 Method 的封裝,經過 proceed() 對方法進行調用。下面舉個例子:

public class AroundMethodInterceptor implements MethodInterceptor {

   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("準備資源");
      try {
         return invocation.proceed();
      } catch (Exception e) {
         System.out.println("監控異常");
         return null;
      } finally {
         System.out.println("資源清理");
      }
   }
   
}
複製代碼

上面實現的 invoke 方法,一會兒把前面說的三種功能都實現了。

以上 4 種 Advice 會在目標對象類的全部實例上生效,被稱爲 per-class 類型的 Advice。還有一種 per-instance 類型的 Advice,可爲實例添加新的屬性或行爲,也就是第一節提到的 Introduction。

(5)Introduction

Spring 爲目標對象添加新的屬性或行爲,須要聲明接口和其實現類,而後經過攔截器將接口的定義和實現類的實現織入到目標對象中。咱們認識下 DelegatingIntroductionInterceptor,其做爲攔截器,當調用新行爲時,會委派(delegate)給實現類來完成。

例如,想在原 MockTask 上進行增強,但不修改類的聲明,可聲明一個新的接口 IReinfore

public interface IReinforce {
   String name = "加強器";
   void fun();
}
複製代碼

再聲明一個接口的實現類:

public class ReinforeImpl implements IReinforce {

    @Override
    public void fun() {
        System.out.println("我變強了,能執行fun方法了");
    }

}
複製代碼

修改下 Main 方法:

public class Main {
   
   public static void main(String[] args) {
      MockTask task = new MockTask();
      ProxyFactory weaver = new ProxyFactory(task);
      weaver.setInterfaces(new Class[]{ITask.class});
      // 爲攔截器指定須要委託的實現類的實例
      DelegatingIntroductionInterceptor delegatingIntroductionInterceptor =
            new DelegatingIntroductionInterceptor(new ReinforeImpl());
      weaver.addAdvice(delegatingIntroductionInterceptor);
      ITask proxyObject = (ITask) weaver.getProxy();
      proxyObject.execute();
      // 使用IReinfore接口調用新的屬性和行爲
      IReinforce reinforeProxyObject = (IReinforce) weaver.getProxy();
      System.out.println("經過使用" + reinforeProxyObject.name);
      reinforeProxyObject.fun();
   }
   
}

/** output: 開始執行任務 任務完成 經過使用加強器 我變強了,能執行fun方法了 **/
複製代碼

代理對象 proxyObject 便經過攔截器,可使用 ReinforeImpl 實現類的方法。

2.3 Aspect

Spring 中用 Advisor 表示 Aspect,不一樣之處在於 Advisor 一般只持有一個 Pointcut一個 AdviceAdvisor 根據 Advice 分爲 PointcutAdvisorIntroductionAdvisor

2.3.1 PointcutAdvisor

經常使用的 PointcutAdvisor 實現類有:

(1) DefaultPointcutAdvisor

最通用的實現類,能夠指定任意類型的 Pointcut除了 Introduction 外的任意類型 Advice

Pointcut pointcut = ...; // 任意類型的Pointcut
Advice advice = ...; // 除了Introduction外的任意類型Advice
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(advice);
複製代碼

(2)NameMatchMethodPointcutAdvisor

在演示 Advice 的代碼中,已經有簡單介紹過,內部有一個 NameMatchMethodPointcut 的實例,可持有除 Introduction 外的任意類型 Advice

Advice advice = ...; // 除了Introduction外的任意類型Advice
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(advice);
複製代碼

(3)RegexpMethodPointcutAdvisor

內部有一個 RegexpMethodPointcut 的實例。

2.3.2 IntroductionAdvisor

只能支持類級別的攔截,和 Introduction 類型的 Advice。實現類有 DefaultIntroductionAdvisor

DelegatingIntroductionInterceptor introductionInterceptor =
				new DelegatingIntroductionInterceptor(new ReinforeImpl());
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(introductionInterceptor, IReinforce.class);
複製代碼

2.4 織入和織入器

在演示 Advice 的代碼中,咱們使用 ProxyFactory 做爲織入器

MockTask task = new MockTask();
// 織入器
ProxyFactory weaver = new ProxyFactory(task);
weaver.setInterfaces(new Class[]{ITask.class});
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(new PrepareResourceBeforeAdvice());
weaver.addAdvisor(advisor);
// 織入,返回代理對象
ITask proxyObject = (ITask) weaver.getProxy();
proxyObject.execute();
複製代碼

ProxyFactory 生成代理對象方式有:

  • 若是目標類實現了某些接口,默認經過動態代理生成。
  • 若是目標類沒有實現接口,默認經過CGLIB生成。
  • 也能夠直接設置ProxyFactory生成的方式,即便實現了接口,也能使用CGLIB。

在以前的演示代碼中,咱們沒有啓動 Spring 容器,也就是沒有使用 Spring IOC 功能,而是獨立使用了 Spring AOP。那麼 Spring AOP 是如何與 Spring IOC 進行整合的?是採用了 Spring 整合最經常使用的方法 —— FactoryBean

ProxyFactoryBean 繼承了 ProxyFactory 的父類 ProxyCreatorSupport,具備了建立代理類的能力,同時實現了 FactoryBean 接口,當經過 getObject 方法得到 Bean 時,將獲得代理類。

2.5 Target

在以前的演示代碼中,咱們直接爲 ProxyFactory 指定一個對象爲 Target。在 ProxyFactoryBean 中不只能使用這種方式,還能夠經過 TargetSource 的形式指定。

TargetSource 至關於爲對象進行了一層封裝,ProxyFactoryBean 將經過 TargetSourcegetTarget 方法來得到目標對象。因而,咱們能夠經過 getTarget 方法來控制得到的目標對象TargetSource 的幾種實現類有:

(1)SingletonTargetSource

很簡單,內部只持有一個目標對象,直接返回。和咱們直接指定對象的效果是同樣的。

(2)PrototypeTargetSource

每次將返回一個新的目標對象實例。

(3)HotSwappableTartgetSource

運行時,根據特定條件,動態替換目標對象類的具體實現。例如當一個數據源掛了,能夠切換至另一個。

(4)CommonsPool2TargetSource

返回有限數目的目標對象實例,相似一個對象池。

(5)ThreadLocalTargetSource

爲不一樣線程調用提供不一樣目標對象

2.6 Joinpoint

終於到了最後的 Joinpoint,咱們經過下面的示例來理解 Joinpoint 的工做機制。

MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
weaver.setInterfaces(new Class[]{ITask.class});
PrepareResourceBeforeAdvice beforeAdvice = new PrepareResourceBeforeAdvice();
ResourceCleanAfterReturningAdvice afterAdvice = new ResourceCleanAfterReturningAdvice();
weaver.addAdvice(beforeAdvice);
weaver.addAdvice(afterAdvice);
ITask proxyObject = (ITask) weaver.getProxy();
proxyObject.execute();

/** output 準備資源 開始執行任務 任務完成 資源清理 **/
複製代碼

咱們知道 getProxy 會經過動態代理生成一個 ITask 的接口類,那麼 execute 方法的內部是如何先執行了 beforeAdvicebefore 方法,接着執行 taskexecute 方法,再執行 afterAdviceafter 方法呢?

答案就在生成的代理類中。在動態代理中,代理類方法調用的邏輯由 InvocationHandler 實例的 invoke 方法決定,那答案進一步鎖定在 invoke 方法

在本示例中,ProxyFactory.getProxy 會調用 JdkDynamicAopProxy.getProxy 獲取代理類。

// JdkDynamicAopProxy
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
複製代碼

getProxy 中爲 newProxyInstanceInvocationHandler 參數傳入 this,即 JdkDynamicAopProxy 就是一個 InvocationHandler 的實現,其 invoke 方法以下:

// JdkDynamicAopProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 經過advised(建立對象時初始化)得到指定的advice
    // 會將advice用相應的MethodInterceptor封裝下
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

    if (chain.isEmpty()) {
        Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
    }
    else {
        // 建立一個MethodInvocation
        MethodInvocation invocation =
            new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        // 調用procced,開始進入攔截鏈(執行目標對象方法和MethodInterceptor的advice)
        retVal = invocation.proceed();
    }
    return retVal;
}
複製代碼

首先得到指定的 advice,這裏包含 beforeAdviceafterAdvice 實例,但會用 MethodInterceptor 封裝一層,爲了後面的攔截鏈。

再建立一個 RelectiveMethodInvocation 對象,最後經過 proceed 進入攔截鏈。

RelectiveMethodInvocation 就是 Spring AOP 中 Joinpoint 的一個實現,其類圖以下:

Joinpoint類圖

首先看下 RelectiveMethodInvocation 的構造函數:

protected ReflectiveMethodInvocation( Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments, @Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
    this.proxy = proxy;
    this.target = target;
    this.targetClass = targetClass;
    this.method = BridgeMethodResolver.findBridgedMethod(method);
    this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
    this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
複製代碼

作了些相關屬性的賦值,而後看向 proceed 方法,如何調用目標對象和攔截器。

public Object proceed() throws Throwable {
    // currentInterceptorIndex從-1開始
    // 當達到已調用了全部的攔截器後,經過invokeJoinpoint調用目標對象的方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // 得到攔截器,調用其invoke方法
    // currentInterceptorIndex加1
    Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
複製代碼

currentInterceptorIndex 從 -1 開始,interceptorsAndDynamicMethodMatchers 裏有兩個攔截器,再因爲減 1,全部調用目標對象方法的條件是currentInterceptorIndex 等於 1。

首先因爲 -1 != 1,會得到包含了 beforeAdviceMethodBeforeAdviceInterceptor 實例, currentInterceptorIndex 加 1 變爲 0。調用其 invoke 方法,因爲是 Before-Advice,因此先執行 beforeAdvicebefore 方法,而後調用 proceed 進入攔截鏈的下一環。

// MethodBeforeAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}
複製代碼

又回到了 proceed 方法,0 != 1,再次得到 advice,此次得到的是包含 afterAdviceAfterReturningAdviceInterceptor實例, currentInterceptorIndex 加 1 變爲 1。調用其 invoke 方法,因爲是 After-Returning-Adivce,因此會先執行 proceed 進入攔截鏈的下一環。

// AfterReturningAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
    Object retVal = mi.proceed();
    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
    return retVal;
}
複製代碼

再次來到 proceed 方法,1 == 1,已調用完全部的攔截器,將執行目標對象的方法。 而後 return 返回,回到 invoke 中,調用 afterAdviceafterReturning

因此在 Joinpoint 的實現中,經過 MethodInterceptor 完成了 目標對象方法和 Advice 的前後執行。

3. 小結

在瞭解了 Spring AOP 的實現後,筆者對 AOP 的概念更加清晰了。在學習過程當中最令筆者感興趣的是 Joinpoint 的攔截鏈,一開始不知道是怎麼實現的,以爲很神奇 😲 。最後學完了,總結下,好像也很簡單,經過攔截器的 invoke 方法和MethodInvocation.proceed 方法(進入下一個攔截器)的相互調用。好像就這麼回事。😛

相關文章
相關標籤/搜索