天天看點

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

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

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

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, 作為一個責任鍊調用. 執行各類通知, 最後傳回執行結果

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

繼續閱讀