5.2 spring5源碼–spring AOP源碼分析二–切面的配置方式

目標:

1. 什麼是AOP, 什麼是AspectJ

2. 什麼是Spring AOP

3. Spring AOP註解版實現原理

4. Spring AOP切面原理解析


 一. 認識AOP及其使用

詳見博文1: 5.1 Spring5源碼–Spring AOP源碼分析一

 

二. AOP的特點

 2.1 Spring AOP

2.1.1 他是基於動態代理實現的

Spring 提供了很多的實現AOP的方式:Spring 介面方式schema配置方式註解的方式. 
如果使用介面方式引入AOP, 就是用JDK提供的動態代理來實現.
如果沒有使用介面的方式引入. 那麼就是使用CGLIB來實現的.

Spring使用介面方式實現AOP, 下面有詳細說明.

研究使用介面方式實現AOP, 目的是為了更好地理解spring使用動態代理實現AOP的兩種方式 

2.1.2 spring3.2以後, spring-core直接把CGLIB和ASM的源碼引入進來了, 所以, 後面我們就不需要再顯示的引入這兩個依賴了.

2.1.3 Spring AOP依賴於Spring ioc容器來管理

2.1.4 Spring AOP只能作用於bean的方法

  如果某個類, 沒有注入到ioc容器中, 那麼是不能被增強的

2.1.5 Spring提供了對AspectJ的支援, 但只提供了部分功能的支援: 即AspectJ的切點解析(表達式)和匹配

我們在寫切面的時候,經常使用到的@Aspect, @Before, @Pointcut, @After, @AfterReturning, @AfterThrowing等就是AspectJ提供的.

我們知道AspectJ很好用, 效率也很高. 那麼為什麼Spring不使用AspectJ全套的東西呢? 尤其是AspectJ的靜態織入.

先來看看AspectJ有哪些特點

AspectJ的特點
1. AspectJ屬於靜態織入. 他是通過修改程式碼實現的. 它的織入時機有三種
    1) Compile-time weaving: 編譯期織入. 例如: 類A使用AspectJ增加了一個屬性. 類B引用了類A, 這個場景就需要在編譯期的時候進行織入, 否則類B就沒有辦法編譯, 會報錯.
    2) Post-compile weaving: 編譯後織入.也就是已經生成了.class文件了, 或者是都已經達成jar包了. 這個時候, 如果我們需要增強, 就要使用到編譯後織入
    3) Loading-time weaving: 指的是在載入類的時候進行織入. 

2. AspectJ實現了對AOP變成完全的解決方案. 他提供了很多Spring AOP所不能實現的功能
3. 由於AspectJ是在實際程式碼運行前就完成了織入, 因此可以認為他生成的類是沒有額外運行開銷的.

擴展: 這裡為什麼沒有使用到AspectJ的靜態織入呢? 因為如果引入靜態織入, 需要使用AspectJ自己的解析器. AspectJ文件是以aj後綴結尾的文件, 這個文件Spring是沒有辦法, 因此要使用AspectJ自己的解析器進行解析. 這樣就增加了Spring的成本. 

 

2.1.6 Spring AOP和AspectJ的比較。由於,Spring AOP基於代理實現. 容器啟動時會生成代理對象, 方法調用時會增加棧的深度。使得Spring AOP的性能不如AspectJ好。

 三. AOP的配置方式

 上面說了Spring AOP和AspectJ. 也說道了AspectJ定義了很多註解, 比如: @Aspect, @Pointcut, @Before, @After等等. 但是, 我們使用Spring AOP是使用純java程式碼寫的. 也就是說他完全屬於Spring, 和AspectJ沒有什麼關係. Spring只是沿用了AspectJ中的概念. 包括AspectJ提供的jar報的註解. 但是, 並不依賴於AspectJ的功能.

 

我們使用的@Aspect, @Pointcut, @Before, @After等註解都是來自於AspectJ, 但是其功能的實現是純Spring AOP自己實現的. 

 

Spring AOP有三種配置方式. 

  • 第一種: 基於介面方式的配置. 在Spring1.2版本, 提供的是完全基於介面方式實現的

  • 第二種: 基於schema-based配置. 在spring2.0以後使用了xml的方式來配置. 

  • 第三種: 基於註解@Aspect的方式. 這種方式是最簡單, 方便的. 這裡雖然叫做AspectJ, 但實際上和AspectJ一點關係也沒有.

因為我們在平時工作中主要使用的是註解的方式配置AOP, 而註解的方式主要是基於第一種介面的方式實現的. 所以, 我們會重點研究第一種和第三種配置方式. 

3.1 基於介面方式的配置. 在Spring1.2版本, 提供的是完全基於介面方式實現的

  這種方式是最古老的方式, 但由於spring做了很好的向後兼容, 所以, 現在還是會有很多程式碼使用這種方式, 比如:聲明式事務

  我們要了解這種配置方式還有另一個原因, 就是我們要看源碼. 源碼里對介面方式的配置進行了兼容處理. 同時, 看源碼的入口是從介面方式的配置開始的.

  那麼, 在沒有引入AspectJ的時候, Spring是如何實現AOP的呢? 我們來看一個例子:

  1. 定義一個業務邏輯介面類

package com.lxl.www.aop.interfaceAop;

/**
 * 使用介面方式實現AOP, 默認通過JDK的動態代理來實現. 非介面方式, 使用的是cglib實現動態代理
 *
 * 業務介面類-- 計算器介面類
 *
 * 定義三個業務邏輯方法
 */
public interface IBaseCalculate {

    int add(int numA, int numB);

    int sub(int numA, int numB);

    int div(int numA, int numB);

    int multi(int numA, int numB);

    int mod(int numA, int numB);

}

 

  2.定義業務邏輯類

package com.lxl.www.aop.interfaceAop;//業務類,也是目標對象

import com.lxl.www.aop.Calculate;

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

/**
 * 業務實現類 -- 基礎計算器
 */

public class BaseCalculate implements IBaseCalculate {

    @Override
    public int add(int numA, int numB) {
        System.out.println("執行目標方法: add");
        return numA + numB;
    }

    @Override
    public int sub(int numA, int numB) {
        System.out.println("執行目標方法: sub");
        return numA - numB;
    }

    @Override
    public int multi(int numA, int numB) {
        System.out.println("執行目標方法: multi");
        return numA * numB;
    }

    @Override
    public int div(int numA, int numB) {
        System.out.println("執行目標方法: div");
        return numA / numB;
    }

    @Override
    public int mod(int numA, int numB) {
        System.out.println("執行目標方法: mod");

        int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB);
        return retVal % numA;
    }
}

 

  3. 定義通知類

  前置通知

package com.lxl.www.aop.interfaceAop;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 定義前置通知
 * 實現MethodBeforeAdvice介面
 */
public class BaseBeforeAdvice implements MethodBeforeAdvice {

    /**
     *
     * @param method 切入的方法
     * @param args 切入方法的參數
     * @param target 目標對象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("===========進入beforeAdvice()============");
        System.out.println("前置通知--即將進入切入點方法");
        System.out.println("===========進入beforeAdvice() 結束============\n");
    }

}

 

  後置通知

package com.lxl.www.aop.interfaceAop;

import org.aspectj.lang.annotation.AfterReturning;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 後置通知
 * 實現AfterReturningAdvice介面
 */
public class BaseAfterReturnAdvice implements AfterReturningAdvice {

    /**
     *
     * @param returnValue 切入點執行完方法的返回值,但不能修改
     * @param method 切入點方法
     * @param args 切入點方法的參數數組
     * @param target 目標對象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("\n==========進入afterReturning()===========");
        System.out.println("後置通知--切入點方法執行完成");
        System.out.println("==========進入afterReturning() 結束=========== ");
    }

}

 

  環繞通知

package com.lxl.www.aop.interfaceAop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 環繞通知
 * 實現MethodInterceptor介面
 */
public class BaseAroundAdvice implements MethodInterceptor {

    /**
     * invocation :連接點
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("===========around環繞通知方法 開始===========");
        // 調用目標方法之前執行的動作
        System.out.println("環繞通知--調用方法之前: 執行");
        // 執行完方法的返回值:調用proceed()方法,就會觸發切入點方法執行
        Object returnValue = invocation.proceed();
        System.out.println("環繞通知--調用方法之後: 執行");
        System.out.println("===========around環繞通知方法  結束===========");
        return returnValue;
    }

}

  配置類

package com.lxl.www.aop.interfaceAop;

import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;

/**
 * 配置類
 */
public class MainConfig {

    /**
     * 被代理的對象
     * @return
     */
    @Bean
    public IBaseCalculate baseCalculate() {
        return new BaseCalculate();
    }

    /**
     * 前置通知
     * @return
     */
    @Bean
    public BaseBeforeAdvice baseBeforeAdvice() {
        return new BaseBeforeAdvice();
    }

    /**
     * 後置通知
     * @return
     */
    @Bean
    public BaseAfterReturnAdvice baseAfterReturnAdvice() {
        return new BaseAfterReturnAdvice();
    }

    /**
     * 環繞通知
     * @return
     */
    @Bean
    public BaseAroundAdvice baseAroundAdvice() {
        return new BaseAroundAdvice();
    }

    /**
     * 使用介面方式, 一次只能給一個類增強, 如果想給多個類增強, 需要定義多個ProxyFactoryBean
     * 而且, 曾增強類的粒度是到類級別的. 不能指定對某一個方法增強
     * @return
     */
    @Bean
    public ProxyFactoryBean calculateProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
        proxyFactoryBean.setTarget(baseCalculate());
        return proxyFactoryBean;
    }

}

 

之前說過, AOP是依賴ioc的, 必須將其註冊為bean才能實現AOP功能

  方法入口

package com.lxl.www.aop.interfaceAop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class InterfaceMainClass{

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        IBaseCalculate calculate = context.getBean("calculateProxy", IBaseCalculate.class);
        System.out.println(calculate.getClass());
        calculate.add(1, 3);
    }

}

 

  執行結果:

===========進入beforeAdvice()============
前置通知--即將進入切入點方法
===========進入beforeAdvice() 結束============

===========around環繞通知方法 開始===========
環繞通知--調用方法之前: 執行
執行目標方法: add
環繞通知--調用方法之後: 執行
===========around環繞通知方法  結束===========

==========進入afterReturning()===========
後置通知--切入點方法執行完成
==========進入afterReturning() 結束=========== 

通過觀察, 我們發現, 執行的順序是: 前置通知–>環繞通知的前置方法 –> 目標邏輯 –> 環繞通知的後置方法 –> 後置通知. 

那麼到底是先執行前置通知, 還是先執行環繞通知的前置方法呢? 這取決於配置文件的配置順序

這裡,我們將環繞通知放在最後面, 所以, 環繞通知在前置通知之後執行. 

  @Bean
    public ProxyFactoryBean calculateProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames( "baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
        proxyFactoryBean.setTarget(baseCalculate());
        return proxyFactoryBean;
    }

那麼, 如果我們將環繞通知放在前置通知之前. 就會先執行環繞通知

  @Bean
    public ProxyFactoryBean calculateProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames("baseAroundAdvice", "baseAfterReturnAdvice", "baseBeforeAdvice");
        proxyFactoryBean.setTarget(baseCalculate());
        return proxyFactoryBean;
    }

 

運行結果

===========around環繞通知方法 開始===========
環繞通知--調用方法之前: 執行
===========進入beforeAdvice()============
前置通知--即將進入切入點方法
===========進入beforeAdvice() 結束============

執行目標方法: add

==========進入afterReturning()===========
後置通知--切入點方法執行完成
==========進入afterReturning() 結束=========== 
環繞通知--調用方法之後: 執行
===========around環繞通知方法  結束===========

 

思考: 使用ProxyFactoryBean實現AOP的方式有什麼問題?

1. 通知加在類級別上, 而不是方法上. 一旦使用這種方式, 那麼所有類都會被織入前置通知, 後置通知, 環繞通知. 可有時候我們可能並不想這麼做

2. 每次只能指定一個類. 也就是類A要實現加日誌, 那麼創建一個A的ProxyFactoryBean, 類B也要實現同樣邏輯的加日誌. 但是需要再寫一個ProxyFactoryBean. 

基於以上兩點原因. 我們需要對其進行改善. 

 

下面, 我們來看看, ProxyFactoryBean是如何實現動態代理的?

ProxyFactoryBean是一個工廠bean, 我們知道工廠bean在創建類的時候調用的是getObject(). 下面看一下源碼

public class ProxyFactoryBean extends ProxyCreatorSupport
        implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
......
   @Override
    @Nullable
    public Object getObject() throws BeansException {
        /**
         * 初始化通知鏈: 將通知放入鏈中
         * 後面初始化的時候, 是通過責任鏈的方式調用這些通知鏈的的. 
         * 那麼什麼是責任鏈呢?
         */
        initializeAdvisorChain();
        if (isSingleton()) {
            /**
             * 創建動態代理
             */
            return getSingletonInstance();
        }
        else {
            if (this.targetName == null) {
                logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
            }
            return newPrototypeInstance();
        }
    }
......
}

 

發送到initializeAdvisorChain是初始化各類型的Advisor通知, 比如, 我們上面定義的通知有三類: “baseAroundAdvice”, “baseAfterReturnAdvice”, “baseBeforeAdvice”. 這裡採用的是責任鏈調用的方式. 

然後調用getSingletonInstance()創建動態代理. 

private synchronized Object getSingletonInstance() {
        if (this.singletonInstance == null) {
            this.targetSource = freshTargetSource();
            if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
                // Rely on AOP infrastructure to tell us what interfaces to proxy.
                Class<?> targetClass = getTargetClass();
                if (targetClass == null) {
                    throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
                }
                setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
            }
            // Initialize the shared singleton instance.
            super.setFrozen(this.freezeProxy);
            /**
             * 創建動態代理
             */
            this.singletonInstance = getProxy(createAopProxy());
        }
        return this.singletonInstance;
    }

調用getProxy(CreateAopProxy())調用代理創建動態代理. 我們這裡使用介面的方式, 這裡調用的是JDKDynamicAopProxy動態代理.

@Override
    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);
    }

 

最終, 動態代理創建, 就是在JDKDynamicAopProxy了.  通過執行Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);創建動態代理實例. 

其實我們通過ctx.getBean(“calculateProxy”)獲得的類, 就是通過JDKDynamicAopProxy創建的動態代理類. 

這裡也看出, 為什麼每次只能給一個類創建動態代理了. 

 

上面提到了責任鏈, 那麼什麼是責任鏈呢? 如下圖所示:

 

 有一條流水線. 比如生產流水線. 裡面有許多道工序. 完成工序1 ,才能進行工序2, 依此類推. 

流水線上的工人也是各司其職. 工人1做工序1, 工人2做工序2, 工人3做工序3…..這就是一個簡單的流水線模型.

工人的責任就是完成每一道工序, 那麼所有工人的責任就是完成這條流水線. 這就是工人的責任鏈.

其實, 我們的通知也是一類責任鏈. 比如, 前置通知, 環繞通知, 後置通知, 異常通知. 他們的執行都是有順序的. 一個工序完成, 做另一個工序.各司其職. 這就是責任鏈.

為了能工統一調度, 我們需要保證, 所有工人使用的都是同一個抽象. 這樣, 就可以通過抽象類的調用方式. 各個工人有具體的工作實現. 

通知也是如此. 需要有一個抽象的通知類Advicor. 進行統一調用.

結合上面的demo, 來看一個責任鏈調用的demo.

上面我們定義了兩個方法. 一個是前置通知BaseBeforeAdvice 實現了MethodBeforeAdvice, 另一個是環繞通知BaseAroundAdvice 實現了MethodInterceptor. 如果想把這兩個通知放在一個鏈上. 那麼他們必須實現相同的介面. 但是, 現在不同. 

我們知道環繞通知, 由兩部分, 一部分是環繞通知的前置通知, 一部分是環繞通知的後置通知. 所以, 我們可以將前置通知看作是環繞通知的前置通知部分.

package com.lxl.www.aop.interfaceAop.chainDemo;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodBeforeAdvice;

/**
 * 為什麼 我們可以讓MethodBeforeAdvice 前置通知繼承自環繞通知的介面呢?
 * 主要原因是, 環繞通知的前半部分, 就是前置通知
 */
public class BeforeAdviceInterceptor implements MethodInterceptor {

  // 前置通知
  MethodBeforeAdvice methodBeforeAdvice;

  public BeforeAdviceInterceptor(MethodBeforeAdvice methodBeforeAdvice) {
    this.methodBeforeAdvice = methodBeforeAdvice;
  }

  /**
   * 使用了環繞通知的前半部分. 就是一個前置通知
   * @param invocation the method invocation joinpoint
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    methodBeforeAdvice.before(invocation.getMethod(), invocation.getArguments(), invocation.getClass());
    return invocation.proceed();
  }
}

 

這段程式碼包裝了前置通知, 讓其擴展為實現MethodInterceptor介面. 這是一個擴展介面的方法. 

接下來我們要創建一條鏈. 這條鏈就可以理解為流水線上各個工人. 每個工人處理一個工序. 為了能夠統一調用. 所有的工人都要實現同一個介面. 責任鏈的定義如下:

    /**
     * 把一條鏈上的都初始化
     *
     * 有一條鏈, 這條鏈上都有一個父類介面 MethodInterceptor.
     * 也就是說, 鏈上都已一種類型的工人. 但每種工人的具體實現是不同的. 不同的工人做不同的工作
     *
     * 這裡定義了一個責任鏈. 連上有兩個工人. 一個是前置通知. 一個是環繞通知.
     * 前置通知和環繞通知實現的介面是不同的. 為了讓他們能夠在一條鏈上工作. 我們自定義了一個MethodBeforeAdviceInterceptor
     * 相當於為BaseBeforeAdvice()包裝了一層MethodInterceptor
     */

    List<MethodInterceptor> list = new ArrayList<>();
    list.add(new BeforeAdviceInterceptor(new BaseBeforeAdvice()));
    list.add(new BaseAroundAdvice());

這裡定義了一個責任鏈. 連上有兩個工人. 一個是前置通知. 一個是環繞通知.

前置通知和環繞通知實現的介面是不同的. 為了讓他們能夠在一條鏈上工作. 我們自定義了一個MethodBeforeAdviceInterceptor

相當於為BaseBeforAdvice()包裝了一層MethodInterceptor

接下來是責任鏈的調用. 

/**
   * 責任鏈調用
   */
  public static class MyMethodInvocation implements MethodInvocation {

    // 這是責任鏈
    protected List<MethodInterceptor> list;
    // 目標類
    protected final BaseCalculate target;

    public MyMethodInvocation(List<MethodInterceptor> list) {
      this.list = list;
      this.target = new BaseCalculate();
    }

    int i = 0;

    public Object proceed() throws Throwable {
      if (i == list.size()) {
        /**
         * 執行到責任鏈的最後一環, 執行目標方法
         */
        return target.add(2, 2);
      }
      MethodInterceptor interceptor = list.get(i);
      i++;
      /**
       * 執行責任鏈調用
       * 這個調用鏈第一環是: 包裝後的前置通知
       * 調用鏈的第二環是: 環繞通知.
       * 都執行完以後, 執行目標方法.
       */
      return interceptor.invoke(this);
    }

    @Override
    public Object getThis() {
      return target;
    }

    @Override
    public AccessibleObject getStaticPart() {
      return null;
    }

    @Override
    public Method getMethod() {
      try {
        return target.getClass().getMethod("add", int.class, int.class);
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      }
      return null;
    }

    @Override
    public Object[] getArguments() {
      return new Object[0];
    }
  }

}

 

 

這裡重點看proceed() 方法. 我們循環獲取了list責任鏈的通知, 然後執行invoke()方法

 

proceed() 方法是一個鏈式循環. 剛開始i=0, list(0)是前置通知, 當調用到前置通知的時候, BeforeAdviceInterceptor.invoke()方法, 又調用了invocation.proceed()方法, 回到了MyMethodInvocation.proceed()方法. 

然後i=1, list(1)是環繞通知, 當調用環繞通知的時候, 又調用了invocation.proceed(); 有回到了MyMethodInvocation.proceed()方法. 

這是已經是list的最後一環了, 後面不會在調用invoke()方法了. 而是執行目標方法. 執行結束以後, 整個調用結束. 

這就是一個調用鏈. 

 對於責任鏈有兩點:

1. 要有一個統一的調用, 也就是一個共同的抽象類.

2. 使用循環或者遞歸, 完成責任鏈的調用

 

總結:

上面這種方式, 使用的是ProxyFactoryBean 代理bean工廠的方式. 他有兩個限制: 

/**
     * 使用介面方式, 一次只能給一個類增強, 如果想給多個類增強, 需要定義多個ProxyFactoryBean
     * 而且, 曾增強類的粒度是到類級別的. 不能指定對某一個方法增強
     * @return
     */
    @Bean
    public ProxyFactoryBean calculateProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
        proxyFactoryBean.setTarget(baseCalculate());
        return proxyFactoryBean;
    }

 

1. 一次只能給1個類增強, 如果給多個類增強就需要定義多個ProxyFactoryBean

2. 增強的粒度只能到類級別上, 不能指定給某個方法增強.

這樣還是有一定的限制.

為了解決能夠在類級別上進行增強, Spring引入了Advisor和Pointcut.

Advisor的種類有很多

RegexpMethodPointcutAdvisor 按正則匹配類
NameMatchMethodPointcutAdvisor 按方法名匹配
DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....)

我們使用按方法名的粒度來增強, 所示使用的是NameMatchMethodPointcutAdvisor

/**
   * Advisor 種類很多:
   * RegexpMethodPointcutAdvisor 按正則匹配類
   * NameMatchMethodPointcutAdvisor 按方法名匹配
   * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
   * InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....)
   */
  @Bean
  public NameMatchMethodPointcutAdvisor aspectAdvisor() {
    /**
     * 通知和通知者的區別:
     * 通知(Advice)  :是我們的通知類 沒有帶切點
     * 通知者(Advisor):是經過包裝後的細粒度控制方式。 帶了切點
     */
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    // 切入點增強的通知類型--前置通知
    advisor.setAdvice(baseBeforeAdvice());
    // 指定切入點方法名--div
    advisor.setMappedNames("div");
    return advisor;
  }

 

這裡設置了切入點需要增強的通知, 和需要切入的方法名. 

這樣就可以對類中的某個方法進行增強了.  我們依然需要使用ProxyFactoryBean()代理工廠類來進行增強

  @Bean
  public ProxyFactoryBean calculateProxy() {
    ProxyFactoryBean userService = new ProxyFactoryBean();
    userService.setInterceptorNames("aspectAdvisor");
    userService.setTarget(baseCalculate());
    return userService;
  }

注意, 這裡增強的攔截器名稱要寫剛剛定義的 NameMatchMethodPointcutAdvisor 類型的攔截器.目標類還是我們的基礎業務類baseCalculate()

這只是解決了可以對指定方法進行增強. 那麼, 如何能夠一次對多個類增強呢? Spring又引入了ProxyCreator.

/**
   * Advisor 種類很多:
   * RegexpMethodPointcutAdvisor 按正則匹配類
   * NameMatchMethodPointcutAdvisor 按方法名匹配
   * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
   * InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....)
   */
  @Bean
  public NameMatchMethodPointcutAdvisor aspectAdvisor() {
    /*
     * 通知和通知者的區別:
     * 通知(Advice)  :是我們的通知類 沒有帶切點
     * 通知者(Advisor):是經過包裝後的細粒度控制方式。 帶了切點
     */
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    // 切入點增強的通知類型--前置通知
    advisor.setAdvice(baseBeforeAdvice());
    // 指定切入點方法名--div
    advisor.setMappedNames("div");
    return advisor;
  }

  /**
   * autoProxy: BeanPostProcessor 手動指定Advice方式,
   * @return
   */
  @Bean
  public BeanNameAutoProxyCreator autoProxyCreator() {
    // 使用bean名字進行匹配
    BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
    beanNameAutoProxyCreator.setBeanNames("base*");
    // 設置攔截鏈的名字
    beanNameAutoProxyCreator.setInterceptorNames("aspectAdvisor");
    return beanNameAutoProxyCreator;
  }

 

如上程式碼, beanNameAutoProxyCreator.setBeanNames(“base*”); 表示按照名字匹配以base開頭的類, 對其使用的攔截器的名稱是aspectAdvisor

 而aspectAdvisor使用的是按照方法的細粒度進行增強. 這樣就實現了, 對以base開頭的類, 對其中的某一個或某幾個方法進行增強. 使用的增強類是前置通知.

下面修改main方法, 看看運行效果

public class InterfaceMainClass{

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        IBaseCalculate calculate = context.getBean("baseCalculate", IBaseCalculate.class);
        calculate.add(1, 3);
        System.out.println("******************");
        calculate.div(1, 3);
    }
}

 

這裡面執行了兩個方法, 一個是add(), 另一個是div(). 看運行結果

執行目標方法: add
******************
===========進入beforeAdvice()============
前置通知--即將進入切入點方法
===========進入beforeAdvice() 結束============
執行目標方法: div

我們看到, add方法沒有被增強, 而div方法被增強了, 增加了前置通知.

 

以上就是使用介面方式實現AOP. 到最後增加了proxyCreator, 能夠根據正則表達式匹配相關的類, 還能夠為某一個指定的方法增強. 這其實就是我們現在使用的註解類型AOP的原型了. 

 3.2 基於註解@Aspect的方式. 這種方式是最簡單, 方便的. 這裡雖然叫做AspectJ, 但實際上和AspectJ一點關係也沒有.

3.2.1 @Aspect切面的解析原理

上面第一種方式詳細研究了介面方式AOP的實現原理. 註解方式的AOP, 最後就是將@Aspect 切面類中的@Befor, @After等註解解析成Advisor. 帶有@Before類會被解析成一個Advisor, 帶有@After方法的類也會被解析成一個Advisor…..其他通知的方法也會被解析成Advisor 在Advisor中定義了增強的邏輯, 也就是@Befor和@After等的邏輯, 以及需要增強的方法, 比如div方法.

下面來分析一下使用註解@Aspect , @Before, @After的實現原理. 上面已經說了, 就是將@Before, @After生成Advisor

這裡一共有三個部分. 

  • 第一部分: 解析@Aspect下帶有@Before等的通知方法, 將其解析為Advisor
  • 第二部分: 在createBean的時候, 創建動態代理
  • 第三部分: 調用. 調用的時候, 執行責任鏈, 循環裡面所有的通知. 最後輸出結果.

下面我們按照這三個部分來分析.

 第一步: 解析@Aspect下帶有@Before等的通知方法, 將其解析為Advisor. 如下圖: 

 第一步是在什麼時候執行的呢? 
在createBean的時候, 會調用很多PostProcessor後置處理器, 在調用第一個後置處理器的時候執行.
執行的流程大致是: 拿到所有的BeanDefinition,判斷類上是不是帶有@Aspect註解. 然後去帶有@Aspect註解的方法中找@Before, @After, @AfterReturning, @AfterThrowing, 每一個通知都會生成一個Advisor

Advisor包含了增強邏輯, 定義了需要增強的方法. 只不過這裡是通過AspectJ的execution的方式進行匹配的.

 第二步: 在createBean的時候, 創建動態代理

 

createBean一共有三個階段, 具體在哪一個階段創建的動態代理呢?

其實, 是在最後一個階段初始化之後, 調用了一個PostProcessor後置處理器, 在這裡生成的動態代理

整體流程是:
在createBean的時候, 在初始化完成以後調用bean的後置處理器. 拿到所有的Advisor, 循環遍歷Advisor, 然後根據execution中的表達式進行matchs匹配. 和當前創建的這個bean進行匹配, 匹配上了, 就創建動態代理. 

pointcut的種類有很多. 上面程式碼提到過的有:

/**
* Advisor 種類很多:
* RegexpMethodPointcutAdvisor 按正則表達式的方式匹配類
* NameMatchMethodPointcutAdvisor 按方法名匹配
* DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
* InstantiationModelAwarePointcutAdvisorImpl 註解解析的advisor(@Before @After....)
*/

 

而我們註解裡面是按照execution表達式的方式進行匹配的

 第三步: 調用. 調用的時候, 執行責任鏈, 循環裡面所有的通知. 最後輸出結果.

 調用的類,如果已經生成了動態代理. 那麼調用的方法, 就是動態代理生成的方法了.然後拿到所有的advisor, 作為一個責任鏈調用. 執行各類通知, 最後返回執行結果