Spring动态代理的背后原理

之前我讲过关于JDK代理和CGLIB代理如何实现无反射调用目标方法,今天咱们聊聊动态代理两个方面:一是代理对象是怎么生成的;二是代理对象的执行过程;

1. 创建代理对象

接下来会对Spring创建代理的步骤进行介绍,如何一步一步创建代理对象的,并且会通过问题和代码的形式来阐明创建代理的过程。

首先我们得理解一下这三者的关系Advisor Advice Aspect:

  • Advisor可以理解成时一种切面,由切点和通知组成,低级切面;
  • Advice是通知概念,也就是需要增强的内容;
  • Aspect是我们通常所知的切面,高级切面;

高级切面低级切面区别主要是前者一个切面可以存在多个通知,而后者一个切面只能存在一个通知。

Spring在第一个Bean加载的时候就会去根据高级切面转换低级Advisor切面, 源码见:org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisors该方法会对Aspect切面类中的每个通知方法(加了Before After等注解)转成一个切面,也就是由高级切面Aspect转成低级切面Advisor的过程;

1.获取Spring容器中的所有Bean的name,收集每个Bean中加了Aspect注解的为切面类;

2.去除该切面类中加了Pointcut注解的方法;

3.在剩下的方法中找到上述五个注解(Around, Before, After, AfterReturning, AfterThrowing)中的任一注解就认为该方法可以转成低级切面;

4.在切面中的通知方法加了多个注解只会有一个生效,优先级根据Around > Before >  After > AfterReturning > AfterThrowing.

5.根据注解转成对应的通知对象, 源码见:org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvice

  • Around  ==>  AspectJAroundAdvice
  • Before   ==>  AspectJMethodBeforeAdvice
  • After  ==>  AspectJAfterAdvice
  • AfterReturning  ==>  AspectJAfterReturningAdvice
  • AfterThrowing  ==>  AspectJAfterThrowingAdvice

6.通知对象和AspectJExpressionPointcut的切点对象形成Advisor低级切面(InstantiationModelAwarePointcutAdvisorImpl);

7.根据切面类形成了一个List存在advisorsCache(ConcurrentHashMap)中,key是切面类的BeanName;

8.在每一个Bean加载的时候,会对Bean中的所有方法(包括private final方法),只要存在匹配List中低级切面的表达式,即为该Bean创建代理对象;(我理解应该排除private和final修饰的方法)

9.当存在有资格的切面时并且切面中存在AspectJ的通知时, 会在所有的切面前面加上一个切面(ExposeInvocationInterceptor),该切面优先级也是最高的,要保证最先执行。 (原因见下文) 源码见:org.springframework.aop.aspectj.AspectJProxyUtils#makeAdvisorChainAspectJCapableIfNecessary.

10.使用ProxyFactory准备创建代理对象,读取模板ProxyFactory,该模板是根据用户的配置生成的。如果不是强制使用CGLIB代理则得检查该类是否存在接口,存在接口塞到ProxyFactory对象中用于后期生成JDK代理对象;

源码见:org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy

11.选择使用代理对象,主要由proxyTargetClass字段控制

  • true  ==> 无论目标类是否实现接口,均使用CGLIB代理;

  • false ==> 目标类存在接口且接口中至少存在一个方法,则选择JDK代理。其他均为CGLIB代理;

Note:SpringBoot 2.x开始使用CGLIB作为默认的代理方式,如果需要使用JDK代理的话,可以通过配置项 spring.aop.proxy-target-class=false实现,基于@EnableAspectJAutoProxy注解修改proxyTargetClass是不能生效的

通常情况下我们总是会看到使用Enhancer的方式创建代理对象,实际上我们也可以使用ProxyFactory来创建代理对象,两者区别是前者比后者更底层。使用ProxyFacotory可以选择创建JDK代理对象(Proxy.newProxyInstance)或者CGLIB代理对象,而使用Enhancer的话是用于创建CGLIB代理对象的,为了方便大家的理解分别写了ProxyFactory和Enhancer去创建代理对象:

1.1 CGLIB创建代理对象

1. 使用ProxyFactory创建代理对象

public class ProxyFactoryDemo {
    
    public static void main(String[] args) {
        //1.基于切面使用ProxyFactory创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory();
        //2.创建目标对象
        ProxyFactoryDemo.Target target = new ProxyFactoryDemo.Target();
        //3.告诉代理对象为哪个目标对象创建代理
        proxyFactory.setTarget(target);

        //4.获取目标类的接口 可用于JDK代理
        proxyFactory.setInterfaces(target.getClass().getInterfaces());

        //5.选择使用JDK代理还是CGLIB代理
        /**
         * 1. false
         *      a).目标类存在接口,则使用JDK代理;
         *      b).目标类不存在接口,则使用CGLIB代理;
         * 2. true
         *      无论是否实现接口均使用CGLIB代理
         */

        proxyFactory.setProxyTargetClass(Boolean.TRUE);

        //6.构建切面 = 切点 + 通知
        //6.1 构建切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* firstTargetMethod())");

        AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
        pointcut2.setExpression("execution(* firstTargetMethod())");
        //6.2 构建通知
        MethodInterceptor methodInterceptor = invocation -> {
            System.out.println("Before ....");
            Object obj = invocation.proceed();
            System.out.println("After ....");
            return obj;
        };
        MethodInterceptor methodInterceptor2 = invocation -> {
            System.out.println("Before 2 ....");
            Object obj = invocation.proceed();
            System.out.println("After 2 ....");
            return obj;
        };

        //6.3 形成切面
        Advisor firstAdvisor = new DefaultPointcutAdvisor(pointcut, methodInterceptor);
        Advisor secondAdvisor = new DefaultPointcutAdvisor(pointcut2, methodInterceptor2);
        proxyFactory.addAdvisors(firstAdvisor, secondAdvisor);

        //7. 生成代理对象
        ProxyFactoryDemo.Target proxyObj = (ProxyFactoryDemo.Target)proxyFactory.getProxy();
        proxyObj.firstTargetMethod();
        proxyObj.finalMethod();
    }


    public static class Target {
        public void firstTargetMethod() {
            System.out.println("Target + firstTargetMethod");
        }
        public void secondTargetMethod() {
            System.out.println("Target + secondTargetMethod");
        }
        public void publicMethod(){
            System.out.println("publicMethod...");
        }
        private void privateMethod(){
            System.out.println("privateMethod...");
        }
        public final void finalMethod(){
            System.out.println("finalMethod...");
        }
    }
}

2.使用Enhancer创建代理对象

public class EnhancerProxyDemo {
    public static void main(String[] args) {
        Target target = new Target();
        /**
         * 1.代理对象本身
         * 2.需要代理的方法
         * 3.代理方法的参数
         * 这里只是生成了一个代理对象,包括该代理对象在执行的情况下的一些逻辑;
         * 另外如果此处的代理对象如果没有执行方法时,是不会执行生成代理对象的。
         */

        Target proxyObject = (Target) Enhancer.create(Target.class, (MethodInterceptor) (proxyObjmethodmethodArgsmethodProxy) -> {
            System.out.println("before...");
            //1.使用目标方法通过反射方式执行
            //Object result = method.invoke(target, methodArgs);

            //2.使用代理方法方式调用  底层无反射 spring的选择
            //Object result = methodProxy.invoke(target, methodArgs);

            //3.使用代理方法父类方法  底层无反射
            Object result = methodProxy.invokeSuper(proxyObj, methodArgs);
            System.out.println("after...");
            
            String returnString = (String) result;
            return returnString + "after Proxy";
        });
        proxyObject.testPrivate();
    }

    //该类被final修饰时时不能生成代理对象的, 会报错
    static class Target {
        //该方法如果被final或者是private修饰的,那么代理对象就无法对该方法实现代理
        public void testPrivate(){
            System.out.println("Target + testPrivate");
        }
    }
}

Spring中存在两个MethodInterceptor

  • org.aopalliance.intercept.MethodInterceptor#invoke   implement MethodInterceptor extend Interceptor extends Advice
  • org.springframework.cglib.proxy.MethodInterceptor#intercept  extends Callback

对于ProxyFactory和Enhancer两者创建代理对象中有个共同的类名:MethodInterceptor, 但是两个类在中相同的类发挥的作用完全不同

  • ProxyFactory中的MethodInterceptor是通知Advice的子子接口(孙子接口),因此是用于创建通知的;

  • Enhancer中的MethodInterceptor是回调函数Callback的子接口,用于代理对象回调进而执行切面逻辑。Spring中使用org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor作为切面的回调对象

a).同一个切面类中的同一个方法只能加一个切面注解,加了多个会怎样?

如果加相同的注解会报错,加不同的注解会按照上述第四点的优先级生效,例如同时加了Before和After时,只有Before的切点会执行通知,After的切点无效。

b).如何控制不同切面的顺序?

可以使用@Order(number)注解的方式,值越小优先级越高;例如A切面的优先级高于B切面,那么A、B两个切面中注解相同的情况下,A的通知先执行。

c).如何控制相同切面内不同通知的顺序?

这种情况下@Order注解是无效的,主要是通过方法名来排序的。例如相同切点的情况下,aaa()通知会在bbb()前执行。

d).如何判断某个Bean是否需要代理?

在上述第七步中会形成一个Map,将Map中的所有value的形成一个大List(candidateAdvisors), 将List中的每一个Advisor和Bean中的每一个方法进行切点表达式匹配,只要有一个命中,就会认为这个Bean需要产生代理对象。

e).Spring为何要将高级Aspect的切面类转成低级Advisor?

高级切面转低级切面是整体碎片化的过程,这样有利于整个AOP精准控制、扩展。

f).Spring如何选择代理(JDK代理还是CGLIB代理)?

见第11步

g). 什么情况下需要创建代理?为啥BeanA不用创建代理对象而BeanB需要?

代理对象在Spring加载的过程中,主要由这个方法org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary完成代理对象的创建。在该方法中如果检测到该Bean中的某个方法存在通知即为该Bean创建代理对象。

1.2 JDK创建代理对象

JDK创建代理对象条件上述已经说明了,另外JDK创建代理对象时是必须要存在接口的,因此在注入这个Bean时,必须得使用接口名去注入,如果使用实现类的话在启动时会报错。

public interface MyInterface {
    
    void firstTargetMethod();
}

@Service
public class BeanC implements MyInterface{

        public void firstTargetMethod(){
        log.info("BeaC + firstTargetMethod");
    }
}

@RestController
public class BeanController {
    
    @Autowired
    private MyInterface myInterface;
}

如果上述的controller注入的是BeanC的话,在启动时会报错的,但是CGLIB代理是没问题

2.代理对象执行过程

上述主要讲述了代理对象在启动时完成加载的主要流程,这一节讲述代理对象在执行过程中如何实现增强的。 为了更好地理解代理对象执行的过程,我分别从横向和纵向来展现代理对象执行的流程。实际上整个执行过程和栈的特性一致,我们都知道栈是先进后出的方式。代理对象执行切面实际上也是这样的,高优先级的切面的before逻辑最先执行,after逻辑最后执行。

Spring动态代理的背后原理





Spring动态代理的背后原理

基于上述的执行方式,举出如下的例子:


        MethodInterceptor methodInterceptor1 = invocation -> {
            System.out.println("Before 1 ....");
            Object obj = invocation.proceed();
            System.out.println("After 1 ....");
            return obj;
        };
        MethodInterceptor methodInterceptor2 = invocation -> {
            System.out.println("Before 2 ....");
            Object obj = invocation.proceed();
            System.out.println("After 2 ....");
            return obj;
        };

对于这两个通知的话,如果methodInterceptor1的优先级比methodInterceptor2高的话,那整个执行的输出的结果就是:
Before 1 ....
Before 2 ...
目标输出
After 2 ....
After 1 ....

基于整个执行流程,对其整个执行链路通过图片的方式呈现出来,便于大家理解。如何去实现这样的过程,我想大家最能想到的就是递归方式,Spring也是通过递归的方式去实现的。

Spring动态代理的背后原理
那接下自己通过代码实现这个过程,大家最好将代码复制到本地然后debug,这样对整个流程理解更加深刻:
public class MyInvocation implements MethodInvocation {

    private final Object targetObject;

    private final Method method;

    private final List<MethodInterceptor> methodInterceptorList;

    private final Object[] arguments;


    public MyInvocation(Object targetObject, Method method, List<MethodInterceptor> methodInterceptorList, Object[] arguments) {
        this.targetObject = targetObject;
        this.method = method;
        this.methodInterceptorList = methodInterceptorList;
        this.arguments = arguments;
    }

    public static void main(String[] args) throws Throwable {

        //构建目标类的对象
        TargetClass targetClass = new TargetClass();
        //构建目标方法
        Method targetMethod = targetClass.getClass().getMethod("targetMethod"null);
        //构建通知List
        MethodInterceptor methodInterceptor1 = invocation -> {
            System.out.println("before 1 ...");
            Object obj = invocation.proceed();
            System.out.println("after 1 ...");
            return obj;
        };
        MethodInterceptor methodInterceptor2 = invocation -> {
            System.out.println("before 2 ...");
            Object obj = invocation.proceed();
            System.out.println("after 2 ...");
            return obj;
        };
        List<MethodInterceptor> methodInterceptors = List.of(methodInterceptor1, methodInterceptor2);

        MyInvocation myInvocation = new MyInvocation(targetClass, targetMethod, methodInterceptors, null);

        myInvocation.proceed();
    }

    @Nonnull
    @Override
    public Method getMethod() {
        return null;
    }

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

    private int index;
    @Nullable
    @Override
    public Object proceed() throws Throwable {
        
        //如果没有切面(size=0)或者前面执行完了 就得执行连接点方法(目标类方法)
        int size = this.methodInterceptorList.size();
        if(index == size){
            return this.method.invoke(this.targetObject, null);
        }
        MethodInterceptor methodInterceptor = this.methodInterceptorList.get(index ++);
        return methodInterceptor.invoke(this);
    }

    @Nullable
    @Override
    public Object getThis() {
        return null;
    }

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


    public static class TargetClass {
        public void targetMethod() {
            System.out.println("TargetClass + targetMethod");
        }
    }
}

2.1CGLIB代理执行

首先看看代理类结构,我只把和目标相关的提取出来的,整个代理类很复杂:

public class BeanC$$EnhancerBySpringCGLIB$$40185bf0 extends BeanC implements SpringProxyAdvisedFactory {

    
     private static final Method CGLIB$firstTargetMethod$0$Method;
     private static final MethodProxy CGLIB$firstTargetMethod$0$Proxy;
    
    
    static void CGLIB$STATICHOOK5() {
        CGLIB$firstTargetMethod$0$Method = ReflectUtils.findMethods(new String[]{"firstTargetMethod""()V"}, (var1 = Class.forName("com.dev.wizard.bean.BeanC")).getDeclaredMethods())[0];
        CGLIB$firstTargetMethod$0$Proxy = MethodProxy.create(var1, var0, "()V""firstTargetMethod""CGLIB$firstTargetMethod$0");
    }

    final void CGLIB$firstTargetMethod$0() {
        super.firstTargetMethod();
    }

        //代理中增强后的目标方法
        public final void firstTargetMethod() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$firstTargetMethod$0$Method, CGLIB$emptyArgs, CGLIB$firstTargetMethod$0$Proxy);
        } else {
            super.firstTargetMethod();
        }
    }
}
  1. 调用增强方法:代理对象执行增强方法时,首先去获取CGLIB$CALLBACK_0; CGLIB$CALLBACK_0是代理对象中的回调属性,在生成代理对象前塞值。代理对象中有很多CallBack对象,我们重点关注的是CGLIB$CALLBACK_0,Spring中的具体实现是DynamicAdvisedInterceptor,其作用是为了构建切面的调用链对象;
Spring动态代理的背后原理
通知转换:在步骤5中会根据注解转成相应的通知,但是其中只有下述三种通知实现了MethodInterceptor接口
  • AspectJAroundAdvice
  • AspectJAfterThrowingAdvice
  • AspectJAfterAdvice

下述两种通知没有实现该接口:

  • AspectJAfterReturningAdvice
  • AspectJMethodBeforeAdvice

实现了该接口即为环绕通知,为了让该两种通知实现了MethodInterceptor接口使用了适配器模式。

  • AspectJMethodBeforeAdvice  ==> MethodBeforeAdviceAdapter(适配器) ==> MethodBeforeAdviceInterceptor
  • AspectJAfterReturningAdvice  ==> AfterReturningAdviceAdapter (适配器) ==> AfterReturningAdviceInterceptor

这种转换是在调用的时候产生。并非在生成代理对象时产生。当然Spring只会在第一次调用的时候会进行通知转换,然后将转换后的通知List存在ConcurrentHashMap中已达到缓存的目的,其中key为method对象。

通过适配器模式进行通知转换 
 public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
  List<MethodInterceptor> interceptors = new ArrayList<>(3);
       //获取切点
  Advice advice = advisor.getAdvice();
  if (advice instanceof MethodInterceptor) {
   interceptors.add((MethodInterceptor) advice);
  }
      //如果是上述三种的通知进行统一转换成MethodInterceptor环绕通知
  for (AdvisorAdapter adapter : this.adapters) {
   if (adapter.supportsAdvice(advice)) {
    interceptors.add(adapter.getInterceptor(advisor));
   }
  }
  if (interceptors.isEmpty()) {
   throw new UnknownAdviceTypeException(advisor.getAdvice());
  }
  return interceptors.toArray(new MethodInterceptor[0]);
 }

我理解这个为了解决历史的兼容性的问题  但是兼容写法值得学习
另外AdvisorAdapter扩展还可以帮助工程师实现自己的切面方式
  1. 构建调用链对象:根据callback对象去构建切面调用链对象,在构建该链对象之前需要获取到通知链,也就是上述所说的MethedInterceptorList,构建完成后调用processed方法实现链式递归调用,源码见org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept
  2. 实现调用:根据调用链对象(CglibMethodInvocation)实现调用;  源码如下:
public Object proceed() throws Throwable {
 // We start with an index of -1 and increment early.
    //与上述我自己的实现方法相比,小编是从0开始的,spring是从-1开始的
 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        //调用目标类的切点方法,也就是待增强的方法
  return invokeJoinpoint();
 }

    //此处spring采用++this.currentInterceptorIndex,小编使用index++,那是因为index是从0开始的,currentInterceptorIndex是从-1开始的
 Object interceptorOrInterceptionAdvice =
   this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
  // Evaluate dynamic method matcher here: static part will already have
  // been evaluated and found to match.
  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 {
   // Dynamic matching failed.
   // Skip this interceptor and invoke the next in the chain.
   return proceed();
  }
 }
 else {
  // It's an interceptor, so we just invoke it: The pointcut will have
  // been evaluated statically before this object was constructed.
        //执行下一个通知时不忘记把调用链对象(this)传递
  return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
 }
}
//执行目标方法
//当methodProxy不为空时 结合目标对象实现无反射调用
//当methodProxy为空时 使用反射调用
protected Object invokeJoinpoint() throws Throwable {
   if (this.methodProxy != null) {
    try {
     return this.methodProxy.invoke(this.target, this.arguments);
    }
    catch (CodeGenerationException ex) {
     logFastClassGenerationFailure(this.method);
    }
   }
   return super.invokeJoinpoint();
  }

在代理对象执行的过程中,你会发现在任何一个代理对象中,第一个切面始终是ExposeInvocationInterceptor,它也是一个环绕通知,如下图所示:

Spring动态代理的背后原理
该类存在的意义:由于后续通知方法在执行时需要获取调用链对象,那么这时就需要通过方法参数传递的形式。对于没有参数传递的方法获取该调用链对象的则可使用ThreadLocal来获取该调用链对象,该类就是充当这个角色。该切面对象的优先级是最高的,调用链必定执行该切面,所以他总是在index为0的位置,如果该通知缺失,会导致后续执行报错,因为获取不到调用链对象则无法往下传递。
    @Override
 @Nullable
 public Object invoke(MethodInvocation mi) throws Throwable {
        //这个第一次get的时候通常为null
  MethodInvocation oldInvocation = invocation.get();
        //将当前调用链塞到ThreadLocal中方便后续方法获取
  invocation.set(mi);
  try {
   return mi.proceed();
  }
  finally {
   invocation.set(oldInvocation);
  }
 }

在生成动态代理对象字节文件时,private,final修饰的方式是不会生成对象的代理方法的,但是对于该类中的其他方法均会生成代理对象,即使不满足切点要求均会生成对应的代理方法和methodProxy对象。

2.2 JDK代理执行过程

public final class $Proxy61 extends Proxy implements MyInterfaceSpringProxyAdvisedDecoratingProxy {

        public final void firstTargetMethod() {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

}

上述h是父类Proxy中的变量 protected InvocationHandler h;

具体实现类是来自org.springframework.aop.framework.JdkDynamicAopProxy
    final class JdkDynamicAopProxy implements AopProxyInvocationHandlerSerializable {
        

    }
Spring动态代理的背后原理当动态代理对象在执行firstTargetMethod方法时,实际上会执行JdkDynamicAopProxy的invoke方法,JDK代理中的InvocationHandler和CGLIB代理中MethodInterceptor的作用是一样的,都是作为执行时的入口构建切面的调用链对象,CGLIB中使用DynamicAdvisedInterceptor执行interceptor方法。

JDK中使用ReflectiveMethodInvocation通过反射的方式调用目标方法,源码见:org.springframework.aop.framework.ReflectiveMethodInvocation#proceed

3. 总结

本文主要分两个方面讲述动态代理,一是创建代理对象,二是代理对象的执行。创建代理对象主要是判断是否有切面匹配Bean中的方法,如果有即为该Bean创建代理对象。代理对象的执行主要通过DynamicAdvisedInterceptor(CGLIB)和JdkDynamicAopProxy(JDK)回调函数,该函数创建了调用链对象,调用链对象中包含该方法的所有通知,执行调用链对象的proceed方法实现递归调用完成代理对象的执行过程。

原文始发于微信公众号(处女座码农):Spring动态代理的背后原理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251631.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!