5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式

盛開的太陽發表於2021-02-05

目標:

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建立的動態代理類. 

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

 

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

5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式

 

 有一條流水線. 比如生產流水線. 裡面有許多道工序. 完成工序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()方法

 5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式

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. 如下圖: 

5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式

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

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

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

 5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式

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

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

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

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

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

 

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

 第三步: 呼叫. 呼叫的時候, 執行責任鏈, 迴圈裡面所有的通知. 最後輸出結果.

5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式

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

 

相關文章