🌑

Shawn Fux

从源码角度分析Spring-AOP原理

也许你曾被人问过 Spring AOP 有几种通知增强模式,我想你如果用过 AOP 那么基本都能答的上来几种前置、后置、环绕,大多时候我们都是使用 AspectJ 注解进行 AOP 切面开发,但是为什么我们写一个注解,Spring 就能帮我们把切面逻辑织入业务代码前后了,通过阅读源码你就发现所谓的前置、后置、环绕其实根本就是一种东西。

Advice & Advisor

什么是 Advice

advice-tj

对于 Spring 的 AOP 来说,最小粒度就是方法级别的,意味着我们只能在一个目标的前后做一些事情,无法真正对方法的内部织入一些切面逻辑,如果你知道代理模式,那么上面的图应该就会好理解些,本次会通过先从 Advice 接口来实现 AOP 切面,而不是使用 AspectJ 注解,其实 AspectJ 的那些注解最终也会转换成接口的实现类,所以学习这些 Advice 接口,更加方便我们理解底层的实现,通过观察下面的类基础结构图:

adivice

  • MethodInterceptor:实现环绕通知的
  • MethodBeforeAdvice:实现前置通知的
  • ThrowsAdvice:抛出异常通知的
  • AfterReturningAdvice:方法返回后通知的
  • IntroductionInterceptor:动态给一个目标添加接口实现

接下来依次实现这些接口,来模拟实现前置,后置,环绕,以及抛异常通知

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
   @Override
   public void before(Method method, Object[] args, Object target) throws Throwable {
      System.out.println("方法执行前");
   }
}

public class MyMethodInterceptor implements MethodInterceptor {
	@Nullable
	@Override
	public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
		System.out.println("环绕通知前");
		Object result = invocation.proceed();
		System.out.println("环绕通知后");
		return result;
	}
}

public class MyAfterReturningAdvice implements AfterReturningAdvice {
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("方法返回后" + returnValue);
	}
}

public class MyThrowsAdvice implements ThrowsAdvice {

	public void afterThrowing(NullPointerException ex) {
		System.out.println("抛异常" + ex);
	}

	public void afterThrowing(IllegalArgumentException ex) {
		System.out.println("抛异常" + ex);
	}
}

ThrowsAdvice 稍微特殊一些,可以定义多个,将来 Spring 会将捕获到的异常类型与你 MyThrowsAdvice 定义的这些异常参数类型进行匹配来决定调用那个方法。在上面我们实现了四个 Advice,接下来就是考虑将我们的切面逻辑织入到我们期望代理的目标方法前后了,这里就需要用 Spring 的提供的一个现成工具类 ProxyFactory 用来帮助我们将 Advice 切面逻辑织入目标对象,并生成代理对象给我们使用。并且 ProxyFactory 帮我们整合了 JDK 和 CgLib 两种生成动态代理的方式。

public class BeanA{
	
	public BeanA a() {
		System.out.println("BeanA::a()方法执行");
		return this;
	}

	public BeanA b(){
		System.out.println("BeanA::b()方法执行");
		throw new NullPointerException();
	}

}

public class PointcutAdvisorDemo {
	public static void main(String[] args) {
    // 被代理对象
		BeanA beanA = new BeanA();
		ProxyFactory factory = new ProxyFactory(beanA);
		factory.addAdvice(new MyAfterReturningAdvice());
		factory.addAdvice(new MyMethodBeforeAdvice());
		factory.addAdvice(new MyMethodInterceptor());
		factory.addAdvice(new MyThrowsAdvice());
		BeanA proxy = (BeanA) factory.getProxy();
		proxy.a();
		System.out.println("=======================================");
		proxy.b();
	}
}

执行结果如下:

方法执行前
环绕通知前
BeanA::a()方法执行
环绕通知后
方法返回后com.demo1.bean.BeanA@24b1d79b
=======================================
方法执行前
环绕通知前
BeanA::b()方法执行
抛异常java.lang.NullPointerException
Exception in thread "main" java.lang.NullPointerException
	at com.demo1.bean.BeanA.b(BeanA.java:21)

通过上面的例子,可以看到我们可以实现类似于使用 AspectJ 注解的效果,为什么说是类似于了,因为此时我们还无法实现精准的切面,就上面的例子来说,如果我只是希望执行 BeanAa() 方法,那又该怎么做了,此时就需要引出我们的 Advisor 了。

什么是 Advisor

advisor

通过上面的继承结果图,可以看到有两个接口继承了它,分别是 PointcutAdvisorIntroductionAdvisor ,那么这两个接口之间有什么区别了?PointcutAdvisor 一般来说搭配 MethodInterceptor、MethodBeforeAdvice、ThrowsAdvice、AfterReturningAdvice 实现对目标方法的逻辑增强,而 IntroductionAdvisor 则是搭配 IntroductionInterceptor 来说实现对类的增强,比方说给某个对象借助动态代理添加接口实现方法。IntroductionAdvisor 属于类级别的增强,而 PointcutAdvisor 属于方法级别的。本节先分析PointcutAdvisor,下一节再分析 IntroductionAdvisor 。先看一下这两个接口都定义了一些什么方法:

public interface Advisor {

   /**
    * 一个空的 Advice 实现,意味这个没有任何切面逻辑
    */
   Advice EMPTY_ADVICE = new Advice() {};


   /**
    * 返回这个 Adivice,即切面逻辑
    */
   Advice getAdvice();

   /**
    * 这个 Spring 目前没有使用到,默认都返回 true
    */
   boolean isPerInstance();

}

public interface PointcutAdvisor extends Advisor {

	/**
	 * 返回这个切点匹配规则
	 */
	Pointcut getPointcut();

}

通过上面的接口分析,我们得出一些结论,那就是 PointcutAdvisor 内部包含了 Advice 和 Pointcut,Advice 我们现在知道了,就是定义切面的逻辑,以及这个切面逻辑在目标的方法执行位置,那么 Pointcut 是什么了?它就是定义了我们的切面逻辑需要切那些类哪些方法:

public interface Pointcut {

   /**
    * 通过 ClassFilter 去判断这个类是否需要切面
    */
   ClassFilter getClassFilter();

   /**
    * 通过 MethodMatcher 去判断这个方法是否需要切面
    */
   MethodMatcher getMethodMatcher();

   /**
    * 一个默认的 Pointcut 实现,即匹配所有类,所有方法
    */
   Pointcut TRUE = TruePointcut.INSTANCE;

}

有了这些信息以后,那么我们就可以接着上面的那个需求,我们希望实现只对 BeanAa() 方法执行切面逻辑:

public class PointcutAdvisorDemo {
   public static void main(String[] args) {
      BeanA beanA = new BeanA();
      ProxyFactory factory = new ProxyFactory(beanA);
      factory.addAdvisor(new PointcutAdvisor() {
         @Override
         public Pointcut getPointcut() {
            return new Pointcut() {
               @Override
               public ClassFilter getClassFilter() {
                  return clazz -> clazz.isAssignableFrom(BeanA.class);
               }

               @Override
               public MethodMatcher getMethodMatcher() {
                  return new MethodMatcher() {
                     @Override
                     public boolean matches(Method method, Class<?> targetClass) {
                        return "a".equals(method.getName());
                     }

                     @Override
                     public boolean isRuntime() {
                        return false;
                     }

                     @Override
                     public boolean matches(Method method, Class<?> targetClass, Object... args) {
                        return false;
                     }
                  };
               }
            };
         }

         @Override
         public Advice getAdvice() {
            return new MyMethodBeforeAdvice();
         }

         @Override
         public boolean isPerInstance() {
            return false;
         }
      });
      BeanA proxy = (BeanA) factory.getProxy();
      proxy.a();
      proxy.b();
   }
}

这里限于版面,就没有把所有的 Advice 都实现添加进行了,只实现了一个 MethodBeforeAdvice,现在我们做一下总结, Advice 定以了切面逻辑,而 Pointcut 定义了那些类那些方法需要被切面,Advisor 则是组合了 Adivce 和 Pointcut,因为一个完整的切面,必须应该有这两个,即切面逻辑是什么(Advice),切那些类和方法(Pointcut)。

引介增强 IntroductionInterceptor

该如何去理解引介增强了?其实就是基于动态代理然后可以为目标类动态实现某个接口,或者动态添加某些方法,先看一下怎么用,然后再分析怎么实现的:

public interface InterfaceB {
	InterfaceB b();
}

public interface InterfaceA {
	InterfaceA a();
}

public class BeanA implements InterfaceA{

	@Override
	public BeanA a() {
		System.out.println("BeanA::a()方法执行");
		return this;
	}

}

public class MyIntroductionInterceptor implements IntroductionInterceptor, InterfaceB {
   @Nullable
   @Override
   public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
      if (implementsInterface(invocation.getMethod().getDeclaringClass())) {
         return invocation.getMethod().invoke(this, invocation.getArguments());
      }
      return invocation.proceed();
   }

   @Override
   public boolean implementsInterface(Class<?> intf) {
      return intf.isAssignableFrom(InterfaceB.class);
   }

   @Override
   public InterfaceB b() {
      System.out.println("MyIntroductionInterceptor::b()方法执行");
      return this;
   }
}

public class IntroductionAdvisor {
	public static void main(String[] args) {
		BeanA beanA = new BeanA();
		ProxyFactory factory = new ProxyFactory(beanA);
		DynamicIntroductionAdvice advice = new MyIntroductionInterceptor();
		Advisor advisor = new DefaultIntroductionAdvisor(advice, InterfaceB.class);
		factory.addAdvisor(advisor);
		InterfaceB proxy = (InterfaceB) factory.getProxy();
		proxy.b();
	}
}

运行结果:

MyIntroductionInterceptor::b()方法执行

可以看到我们明明设置 target 对象是一个 BeanA ,而 BeanA 并未实现 InterfaceB 接口,那么为什么可以强制转换为 InterfaceB 类型的对象了,并且还可以调用 b() 方法,给我们的感觉就是好像给 BeanA 这个对象动态添加了一个 b() 方法一样。那么究竟是怎么做到的了?我们可以试着将 proxy 对象反编译一下,看看源码到底是怎么样的?

public final class $Proxy0
extends Proxy
implements InterfaceA,
InterfaceB,
SpringProxy,
Advised,
DecoratingProxy {

可以看到我们最终拿到的代理对象实现了 InterfaceA 和 InterfaceB 接口,那么我强制转换成 InterfaceB 理所当然的不会有问题,至于 b() 方法的实现,我们只需要在 proxy 这个对象里面持有 MyIntroductionInterceptor 对象的引用即可,那么将来我调用 b() 方法,我去调用 MyIntroductionInterceptor 对象的 b() 方法即可。但是从$Proxy0 的继承实现关系中也可以看出一些问题,我们只能将这个 proxy 对象强制转换成 InterfaceA 或 InterfaceB,无法强制转换成 BeanA,那么假如我 BeanA 有一些属于自己的方法,就无法调用到了,怎么解决了?使用 Cglib ,继承 BeanA 再实现 InterfaceA 和 InterfaceB 这样,我们就能鱼与熊掌都可兼得。

ProxyFactory 源码解析

public class PointcutAdvisorDemo {
   public static void main(String[] args) {
      BeanA beanA = new BeanA();
      ProxyFactory factory = new ProxyFactory(beanA);
      factory.addAdvice(new MyMethodBeforeAdvice());
      factory.addAdvice(new MyAfterReturningAdvice());
      factory.setProxyTargetClass(true);
      BeanA proxy = (BeanA) factory.getProxy();
      proxy.a();
   }
}

首先还是以上面这个示例代码分析起,然后查看这些代码的内部每一步都做了些什么,是如何把我们的切面逻辑织入方法前后的。

new ProxyFactory

我们先看一下调用构造函数创建一个 ProxyFactory 对象的时候内部发生了什么,我们把一个被代理对象 BeanA 通过构造函数参数传递给了 ProxyFactory :

public ProxyFactory(Object target) {
   // 将 target 包装成一个 TargetSource 而不是直接引用 target
   setTarget(target);
   // 解析 target 对象实现了那些接口,并添加到一个数组
   // 生成的代理对象将会实现这些接口
   setInterfaces(ClassUtils.getAllInterfaces(target));
}

这里他为什么不是直接引用 BeanA 这个对象,而是通过 TargetSource 去包装了?个人猜测是为了延迟创建,比方说有些对象不方便现在直接给你,但是当你真正需要的时候你再去通过 TargetSource 找我拿。当然 ProxyFactory 不止只有这一个构造方法,这里就不一一展开讲了。

addAdvice

接下来我们分析一下 ProxyFactory 的 addAdvice 方法,看他是如何把我们定义的切面逻辑添加进去内部保存的。

private List<Advisor> advisors = new ArrayList<>();

public void addAdvice(Advice advice) throws AopConfigException {
   int pos = this.advisors.size();
   addAdvice(pos, advice);
}

第一步:获取 advisors 的 size,然后调用了另一个重载方法 addAdvice 将当前 advisors 的长度作为 pos 参数传进去了,那么这个 pos 是什么了?pos 就是可以指定你要将这个 advice 存在 List 的那个索引位置,像我们这种情况,就是获取 List 的 size 作为 pos,其实就是你不指定 pos ,那么就是存放到 List 的最末尾。

第二步:通过下面的代码分析,我们的 Advice 最终会包装成一个 DefaultPointcutAdvisor

public void addAdvice(int pos, Advice advice) throws AopConfigException {
   Assert.notNull(advice, "Advice must not be null");
   if (advice instanceof IntroductionInfo) {
      // 如果 advice 实现了 IntroductionInfo 接口
      // 包装成一个 DefaultIntroductionAdvisor
      addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
   }
   else if (advice instanceof DynamicIntroductionAdvice) {
      throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
   }
   else {
      // 当上面的条件不满足,包装成一个 DefaultPointcutAdvisor
      addAdvisor(pos, new DefaultPointcutAdvisor(advice));
   }
}

那么我们来看一下这个 DefaultPointcutAdvisor 对象都做了一些什么,它是怎么包装我们的 Advice 的:

public DefaultPointcutAdvisor(Advice advice) {
   this(Pointcut.TRUE, advice);
}

在 DefaultPointcutAdvisor 指定了一个默认的 Pointcut,而这个 Pointcut.TRUE 就是 Pointcut 接口的一个默认实现类,这个默认实现类就是匹配所有类匹配所有方法,所以这也就是如果我们只是单纯添加 Advice 的话,就会去匹配所有的类所有的方法,当然某些方法不会匹配到的,比方说 Object 类的 equals hashCode toString 等方法,后面源码会看到的。

第三步:factory.setProxyTargetClass(true); 就是强制指定使用 Cglib 生成代理对象。

factory.getProxy()

public Object getProxy() {
   return createAopProxy().getProxy();
}

首先会通过 createAopProxy() 获取一个创建代理对象的工具类,在 Spring 提供了两种创建代理对象的分别是 CglibAopProxyJdkDynamicAopProxy

protected final synchronized AopProxy createAopProxy() {
   if (!this.active) {
      activate();
   }
   return getAopProxyFactory().createAopProxy(this);
}

然后通过工厂 AopProxyFactory 去获得一个创建代理对象工具类,前面说了总共有两种工具类,所以在 createAopProxy 方法就会去决策到底使用哪一个工具类来去生成代理对象 JDK 或 CGLIB

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   // NativeDetector.inNativeImage() 如果虚拟机环境是 GraalVM,那么使用JDK动态代理
   // config.isOptimize() 判断是否开启性能优化,默认false
   // config.isProxyTargetClass() 判断是否使用 CGlib,如果返回 true,使用 CGLIB
   // hasNoUserSuppliedProxyInterfaces(config) 判断是否没有实现接口,如果没有使用CGLIB
   if (!NativeDetector.inNativeImage() &&
         (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
         throw new AopConfigException("TargetSource cannot determine target class: " +
               "Either an interface or a target is required for proxy creation.");
      }
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

new ObjenesisCglibAopProxy

ObjenesisCglibAopProxy 继承自 CglibAopProxy,而且它的构造函数也是直接调用父类的,所以我们重点看下父类 CglibAopProxy 在构造函数做了些什么:

public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
   Assert.notNull(config, "AdvisedSupport must not be null");
   if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
      throw new AopConfigException("No advisors and no TargetSource specified");
   }
   this.advised = config;
   this.advisedDispatcher = new AdvisedDispatcher(this.advised);
}

在创建 CglibAopProxy 对象的时候,会去检查这个 AdvisedSupport config 对象是否有设置 Advisor 和 TargetSource 是否为空,其实这个 AdvisedSupport 就是我们的 ProxyFactory,因为 ProxyFactory 继承了 AdvisedSupport。

new JdkDynamicAopProxy

public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
   Assert.notNull(config, "AdvisedSupport must not be null");
   if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
      throw new AopConfigException("No advisors and no TargetSource specified");
   }
   this.advised = config;
   // 获取代理对象要实现的接口,除了我们设置的,还会自带一些默认的
   // 比如 SpringProxy Advised DecoratingProxy
   this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
   // 判断接口是否有 equals hashCode 方法
   findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}

相较于 CGLIB ,在创建 JdkDynamicAopProxy 的时候还额外多做了两件事,因为 JDK 是基于接口进行动态代理的,所以他会在构造函数找到所有要实现的接口,并且会去检查接口中是否有 equals hashCode 方法,需要额外特殊处理。

invoke & intercept

通过上面的步骤我们拿到 JDK 或 CGLIB 的代理对生成器,我们就可以获得一个代理对象了,这里我就以 CGLIB 为例去分析,他是怎么处理我们的 Advisor 然后去对我们的目标做拦截,因为不管是 JDK 或是 CGLIB 在这个流程的思路都基本一样,如果你使用 CGLIB 对一个代理对象的方法进行 Debug 跟踪的话,应该就会进入到 CglibAopProxy.class 类的 intercept 方法,而如果使用的是 JDK 的话,那么会进入到 JdkDynamicAopProxy.class 类的 invoke 方法。

// JdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
   // 使用 TargetSource 来包装目标被代理对象,而不是直接引用被代理对象
   TargetSource targetSource = this.advised.targetSource;
   Object target = null;

   try {
      // 判断是不是 equals 方法,如果是不直接代理拦截
      if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
         return equals(args[0]);
      }
      // 判断是不是 hashCode,如果是不直接代理拦截
      else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
         return hashCode();
      }
      else if (method.getDeclaringClass() == DecoratingProxy.class) {
         // There is only getDecoratedClass() declared -> dispatch to proxy config.
         return AopProxyUtils.ultimateTargetClass(this.advised);
      }
      else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
            method.getDeclaringClass().isAssignableFrom(Advised.class)) {
         // Service invocations on ProxyConfig with the proxy config...
         return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
      }

      Object retVal;
      // 是否需要将当前代理对象放入 ThreadLocal
      if (this.advised.exposeProxy) {
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }

      // 获取被代理的目标对象和 Class 类型
      target = targetSource.getTarget();
      Class<?> targetClass = (target != null ? target.getClass() : null);

      // 获取被代理对象的方法拦截器调用链
      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 invocation =
               new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         // 递归调用 proceed 方法,处理每一个拦截器链
         retVal = invocation.proceed();
      }

      Class<?> returnType = method.getReturnType();
      // 如果方法的返回值是类型是和被代理对象的类型一样
      // 如果方法所在的类的没有实现 RawTargetAccess 接口,那么返回 this 返回的就是代理对象
      // 反之返回目标原始对象
      if (retVal != null && retVal == target &&
            returnType != Object.class && returnType.isInstance(proxy) &&
            !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
         retVal = proxy;
      }
      else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
         // 如果返回值不为等于 null,但是方法的返回值又不是 void,且方法的返回值是基本数据类型 int long 这种
         // 那么抛出异常
         throw new AopInvocationException(
               "Null return value from advice does not match primitive return type for: " + method);
      }
      return retVal;
   }
   finally {
      if (target != null && !targetSource.isStatic()) {
         // Must have come from TargetSource.
         targetSource.releaseTarget(target);
      }
      if (setProxyContext) {
         // Restore old proxy.
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}
// CglibAopProxy.class
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
   Object target = null;
   // 将当前的被代理对象包装成 TargetSource,而不是直接引用被代理对象
   TargetSource targetSource = this.advised.getTargetSource();
   try {
      // 判断是不是需要将当前代理对象放入 ThreadLocal
      // 可以通过 AopContext.currentProxy() 获取当前代理对象
      if (this.advised.exposeProxy) {
         // Make invocation available if necessary.
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
      // 获取被代理对象
      target = targetSource.getTarget();
      // 获取被代理对象的 Class
      Class<?> targetClass = (target != null ? target.getClass() : null);
      // 获取所有与当前对象的方法所匹配的 Advice,形成一条调用链
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      Object retVal;
      // 检查是否有方法拦截器可执行,并且方法是公开可访问的
      if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
         // 如果没找到对应的方法拦截器可以使用,那么直接反射调用目标方法
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = methodProxy.invoke(target, argsToUse);
      }
      else {
         // 创建一个 MethodInvocation 递归调用方法拦截器
         retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
      }
      // 处理返回值
      retVal = processReturnType(proxy, target, method, retVal);
      return retVal;
   }
   finally {
      if (target != null && !targetSource.isStatic()) {
         targetSource.releaseTarget(target);
      }
      if (setProxyContext) {
         // Restore old proxy.
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

重点分析一下 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 这一行代码做了些什么,在这里会将我们的 Adivice 适配包装成 MethodInterceptor 接口类型,方便后面的统一调用。

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

	private final MethodBeforeAdvice advice;

	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}


	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}

}

可以看到我们的 MethodBeforeAdvice 最终会被包装成 MethodBeforeAdviceInterceptor ,其实就是一个 MethodInterceptor,像 AfterReturningAdvice ThrowsAdvice 最终都会是一个 MethodInterceptor。

public DefaultAdvisorAdapterRegistry() {
   registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
   registerAdvisorAdapter(new AfterReturningAdviceAdapter());
   registerAdvisorAdapter(new ThrowsAdviceAdapter());
}

可以看到在 DefaultAdvisorAdapterRegistry 注册了三个适配器分别用来适配包装成我们的 MethodInterceptor。

当我们找到目标方法的对应的方法拦截器,然后就会交给 ReflectiveMethodInvocation.class 这个类的 proceed 方法会对我们的拦截器进行递归调用

public Object proceed() throws Throwable {
		// 递归调用 proceed 方法,每调用一次 currentInterceptorIndex++
		// 当调用完 interceptorsAndDynamicMethodMatchers 数组内的所有方法拦截器
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			// 调用被代理对象的目标方法,递归的终点
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// 动态方法需要在真正被调用的时候才能进行匹配
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// 没有匹配到,跳过执行下一个
				return proceed();
			}
		}
		else {
			// 把当前自身传递给 invoke方法,自身 this 是一个实现了 MethodInvocation 接口的类
			// 每个invoke方法执行完最后又会调用 invocation.proceed() 相当于调用 this.proceed() 一个递归调用
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

小结

通过前面两小结的分析,我们现在可以得出一些信息,Advice 用来定义切面逻辑以及切方法的那个位置,而 Pointcut 则是定义了某个 Advice 能够匹配那些类那些方法,最终会通过 Advisor 将 Advice 和Pointcut 聚合在一起形成一个完整的切面。最后我们也了解到无论是 MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice 最终都会使用适配器模式包装成 MethodInterceptor 接口类型。下面我们就可以着手分析在 Spring 创建 Bean 的过程中,是怎么进行干预为我们生成代理对象的,像 AspectJ 这些 @Before @Around @After @AfterReturning @AfterThrowing 等注解到底是怎么实现的?

启用 @AspectJ 注解切面支持

通常来说我们都是使用基于 AspectJ 提供的那一套注解来进行定义切面信息,首先我们需要引入 aspectjweaver.jar 包,其实 Spring 的 AOP 只是借用了 AspectJ 注解,但其底层实现运行时仍然是纯 Spring AOP 基于动态代理实现的,并不依赖于 AspectJ 编译器或编织器,这一点很多人容易搞混。

@EnableAspectJAutoProxy

要使用 AspectJ 注解定义切面信息并且生效的话,需要在配置类上加上 @EnableAspectJAutoProxy 注解,我们可以点击查看这个注解内部做了些什么:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

   /**
    * 是否使用 CGLIB 创建代理对象
    * 
    */
   boolean proxyTargetClass() default false;

   /**
    * 是否需要将代理对象放入到 ThreadLcoal
    */
   boolean exposeProxy() default false;

}

可以看到,在其注解内部定义了这两个属性,其实这些属性最终会赋值到 ProxyFactory 从而影响到我们在创建代理对象时的一些行为。而最主要的 @Import(AspectJAutoProxyRegistrar.class) 这一行信息,导入了一个注册类,然后这个注册类会注册一个 AnnotationAwareAspectJAutoProxyCreator 类,分析继承实现关系图,可以看到其实这个类是一个 BeanPostProcessor Bean后置处理器,而这个类就是实现我们注解 AOP 的关键,其实大概也能猜出个一二,首先肯定会找出所有带 @AspectJ 注解的 Bean,然后解析成一个个 Advisor,最后在创建 Bean 的时候去和这些 Advisor 匹配,如果匹配上了就生成代理对象返回。

AnnotationAwareAspectJAutoProxyCreator

所以接下来,就分析 AnnotationAwareAspectJAutoProxyCreator 这个类是如何工作的。

切点表达式

我们使用 @Pointcut 来定义一个切点表达式,其实就相当于我们的 Pointcut 接口,作为切入点签名的方法必须有 void 返回类型,那么在 Spring AOP 中支持那些切点表达式了?

切点类型 说明
execution 用于匹配方法执行连接点。这是使用 Spring AOP 时使用的主要切入点指示符。
within 限制匹配到特定类型内的连接点(使用 Spring AOP 时执行匹配类型内声明的方法)。
this 限制匹配到连接点(使用 Spring AOP 时方法的执行),其中 bean 引用(Spring AOP 代理)是给定类型的实例。
target 将匹配限制在目标对象(被代理的应用程序对象)是给定类型的实例的连接点(使用 Spring AOP 时方法的执行)。
args 限制匹配到参数是给定类型的实例的连接点(使用 Spring AOP 时方法的执行)。
@target 限制匹配到连接点(使用 Spring AOP 时方法的执行),其中执行对象的类具有给定类型的注释。
@args 将匹配限制为连接点(使用 Spring AOP 时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。
@within 将匹配限制为具有给定注释的类型内的连接点(使用 Spring AOP 时执行在具有给定注释的类型中声明的方法)。
@annotation 限制匹配到连接点的主题(在 Spring AOP 中运行的方法)具有给定注释的连接点。
bean 限制匹配到 BeanName,可以支持*通配符

切点位置

通过 @Pointcut 注解的切点表达式,能够知道那些类和方法需要被切,但是切在方法的什么位置,则是由以下注解决定的,相当于我们接口中的 Advice。

切点注解 说明
@Before 目标方法调用之前的通知
@AfterReturning 当目标方法正常执行完返回之后的通知
@AfterThrowing 当目标方法执行时发生异常的通知
@After 类似于 try-catch 语句中的 finally 块的执行位置
@Around 环绕通知
@DeclareParents 为类新增一个接口方法实现,相当于引介增强 IntroductionInterceptor

创建代理对象的位置

在了解 Spring AOP 之前,建议先了解一下 Bean 的生命周期,可以看一下我写的 Bean的生命周期 。Spring 的 AOP 是基于 BeanPostProcessor 这种扩展机制实现的,在创建完 Bean 且填充完属性执行完初始化回调之后,会回调 BeanPostProcessor 接口的 postProcessAfterInitialization 方法,而 AOP 就是在这里执行返回代理对象的。而其中一个实现类 AbstractAutoProxyCreator ,就是实现我们 AOP 的关键,而我们使用 @EnableAspectJAutoProxy 注入的 AnnotationAwareAspectJAutoProxyCreator 类则继承了 AbstractAutoProxyCreator 类。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      // 首先检查 earlyProxyReferences 是否存在该 Bean
      // 如果存在说明已经经过 AOP了,那么就不需要 AOP了
      // 什么情况下会提早进行 AOP 了,当出现循环依赖的时候
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

上面的代码就是 AbstractAutoProxyCreator 类实现 BeanPostProcessor 接口的 postProcessAfterInitialization 方法,首先会去检查 earlyProxyReferences 这个 Map 集合是否存在该 Bean,如果存在说明已经被 AOP 了,如果没有才会去调用 wrapIfNecessary 方法进一步判断是否需要 AOP,接下来重点分析 wrapIfNecessary 方法。

wrapIfNecessary 判断是否需要 AOP

protected Object  wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   // 先检查 advisedBeans 是否已经解析过该Bean
   // 并且解析结果为 FALSE 则不需要 AOP
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   // 判断 bean 是不是 Advice Pointcut Advisor AopInfrastructureBean 其中之一的类型
   // 如果是的话则不需要 AOP
   // shouldSkip 判断 beanName 是否以 全限定类名加.ORIGINAL 组成,如果是的话跳过不 AOP
   // shouldSkip 判断该 bean 是不是一个切面类,即被 @Aspect 注解修饰
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 查找是否有与该 Bean 相匹配的 Advisor
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 将匹配到的 Advisor 创建成一个代理对象
      Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}

小结

当我们要使用 Aspect 注解来进行 AOP切面编程的时候,只需要在配置类上加一个 @EnableAspectJAutoProxy 注解会注册一个 AnnotationAwareAspectJAutoProxyCreator 类,而这个类就会去容器内查找所有 @Aspect 注解标注的 Bean,然后解析里面的 @Pointcut @Before @After 等注解标信息及方法,包装成一个个 Advisor。虽然这样总结起来看似很简单,但其实现内部绝不是三言两语说的清的,这里就不细展开了。

— Sep 7, 2022