【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)(上)

简介: 【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)(上)

前言


在前面的博文我们了解到,Spring所有的代理AopProxy的创建最后都是ProxyCreatorSupport#createAopProxy这个方法,而这个方法如下:


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


显然它又是调用了AopProxyFactory#createAopProxy方法,它的唯一实现为DefaultAopProxyFactory。

它做了一个简单的逻辑判断:若实现类接口,使用JdkDynamicAopProxy最终去创建,否则交给ObjenesisCglibAopProxy。


最终拿到AopProxy后,调用AopProxy#getProxy()就会拿到这个代理对象,从而进行相应的工作了。


我们基本有一共共识就是:默认情况下,若我们实现了接口,就实用JDK动态代理,若没有就实用CGLIB。那么就下来,就具体看看关乎到代理对象的创建、执行的一个具体过程原理


常识普及


AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。


AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。


静态代理


静态代理是编译阶段生成AOP代理类,也就是说生成的字节码就织入了增强后的AOP对象;(并不会创建出多余的对象)


实现方式:


  • 包装器模式:持有目标对象的引用,然后实际上是调用目标对象的方法。 这种方式也可称为代理模式,但是有明显的缺点(比如一般都需要实现同一个接口,且它是以编码的方式去实现的,侵入性高)
  • AspectJ静态代理方式:非常非常强大。Aspectj并不是动态的在运行时生成代理类,而是在编译的时候就植入代码到class文件。由于是静态织入的,所以性能相对来说比较好。Aspectj不受类的特殊限制,不管方法是private、或者static、或者final的,都可以代理,Aspectj不会代理除了限定方法之外任何其他诸如toString(),clone()等方法,唯一缺点就是必须有AspectJ自己的编译器的支持,所以其实很少使用 Spring也是提供了相关类支持的,比如:LoadTimeWeaverAwareProcessor


基于AspectJ的静态代理方式非常强大,但是它依赖于它自己的编译器。并且还有自己的个性化语言,使用起来不够方便,因此其实还是使用得较少的。主要还是以动态代理为主~~~


动态代理

动态代理则不会修改字节码,而是在内存中临时生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法


这在我们平时使用中得到了大量的使用,因为使用简单并且还非常灵活,下面就重点介绍。


AopProxy:Aop代理接口


它是一个AOP代理的抽象接口。提供了两个方法,让我们可以获取对应 配置的AOP对象的代理:


public interface AopProxy {
  //Create a new proxy object. Uses the AopProxy's default class loader  ClassUtils.getDefaultClassLoader()
  Object getProxy();
  Object getProxy(@Nullable ClassLoader classLoader);
}


它的继承关系也很简单,就是接下来我们要说的那几个


image.png


环境构建


为了更好的做源码分析,因此此处我构建一个非常简单的Spring AOP的环境,来跟踪一下它的来龙去脉


public class Main {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) ->
                System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1)));
        DemoInterface demo = (DemoInterface) proxyFactory.getProxy();
        //你被拦截了:方法名为:hello 参数为--[]
        //this demo show
        demo.hello();
        System.out.println(proxyFactory.getTargetClass()); //class com.fsx.maintest.Demo
        System.out.println(proxyFactory.getTargetSource()); //SingletonTargetSource for target object [com.fsx.maintest.Demo@643b1d11]
        System.out.println(Arrays.asList(proxyFactory.getProxiedInterfaces())); //[interface com.fsx.maintest.DemoInterface]
        System.out.println(Arrays.asList(proxyFactory.getAdvisors())); //[org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.fsx.maintest.Main$$Lambda$2/1349414238@2ef5e5e3]]
        // 获取类型,看看是JDK代理还是cglib的
        System.out.println(demo instanceof Proxy); //true  所有的JDK代理都是继承自Proxy的
        System.out.println(demo instanceof SpringProxy); //true
        System.out.println(demo.getClass()); //class com.fsx.maintest.$Proxy0
        System.out.println(Proxy.isProxyClass(demo.getClass())); //true
        System.out.println(AopUtils.isCglibProxy(demo)); //false
        //测试Advised接口、DecoratingProxy的内容
        Advised advised = (Advised) demo;
        System.out.println(Arrays.asList(advised.getProxiedInterfaces())); //[interface com.fsx.maintest.DemoInterface]
        System.out.println(Arrays.asList(advised.getAdvisors())); //[org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.fsx.maintest.Main$$Lambda$2/1349414238@2ef5e5e3]]
        System.out.println(advised.isExposeProxy()); //false
        System.out.println(advised.isFrozen()); //false
        //System.out.println(advised.removeAdvice(null));
        DecoratingProxy decoratingProxy = (DecoratingProxy) demo;
        System.out.println(decoratingProxy.getDecoratedClass()); //class com.fsx.maintest.Demo
        System.out.println("-----------------------------------------------------");
        // Object的方法 ==== 所有的Object方法都不会被AOP代理 这点需要注意
        System.out.println(demo.equals(new Object()));
        System.out.println(demo.hashCode());
        System.out.println(demo.getClass());
        // 其余方法都没被拦截  只有toString()被拦截了  咋回事呢?它也不符合切点表达式的要求啊  看下面的解释吧
        // 你被拦截了:方法名为:hello 参数为--[]
        // com.fsx.maintest.Demo@643b1d11
        System.out.println(demo.toString());
    }
}
interface DemoInterface {
    void hello();
}
class Demo implements DemoInterface {
    @Override
    public void hello() {
        System.out.println("this demo show");
    }
}


因为本文只讲述代理的创建,所以使用最为简单的ProxyFactory来创建代理,排除其它的干扰因素

JdkDynamicAopProxy


如上搭建的环境,生成的就是JDK的动态代理对象。这里面逻辑比较干净简单了:


1.setTarget和setInterfaces交给ProxyCreatorSupport


2.addAdvice:此处就是个Advice前置通知增强器。最终会被包装成DefaultPointcutAdvisor交给ProxyCreatorSupport


3.getProxy()才是今天的关键,他内部会去new出来一个JdkDynamicAopProxy,然后调用其getProx()获取到动态代理出来的对象


那么接下来,就深入它的源码看看究竟:


// 我们发现它自己就实现了了InvocationHandler,所以处理器就是它自己。会实现invoke方法
// 它还是个final类  默认是包的访问权限
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
  private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
  /** 这里就保存这个AOP代理所有的配置信息  包括所有的增强器等等 */
  private final AdvisedSupport advised;
  // 标记equals方法和hashCode方法是否定义在了接口上=====
  private boolean equalsDefined;
  private boolean hashCodeDefined;
  public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
    Assert.notNull(config, "AdvisedSupport must not be null");
    // 内部再校验一次:必须有至少一个增强器  和  目标实例才行
    if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
      throw new AopConfigException("No advisors and no TargetSource specified");
    }
    this.advised = config;
  }
  @Override
  public Object getProxy() {
    return getProxy(ClassUtils.getDefaultClassLoader());
  }
  // 真正创建JDK动态代理实例的地方
  @Override
  public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
      logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    // 这部很重要,就是去找接口 我们看到最终代理的接口就是这里返回的所有接口们(除了我们自己的接口,还有Spring默认的一些接口)  大致过程如下:
    //1、获取目标对象自己实现的接口们(最终肯定都会被代理的)
    //2、是否添加`SpringProxy`这个接口:目标对象实现对就不添加了,没实现过就添加true
    //3、是否新增`Adviced`接口,注意不是Advice通知接口。 实现过就不实现了,没实现过并且advised.isOpaque()=false就添加(默认是会添加的)
    //4、是否新增DecoratingProxy接口。传入的参数decoratingProxy为true,并且没实现过就添加(显然这里,首次进来是会添加的)
    //5、代理类的接口一共是目标对象的接口+上面三个接口SpringProxy、Advised、DecoratingProxy(SpringProxy是个标记接口而已,其余的接口都有对应的方法的)
    //DecoratingProxy 这个接口Spring4.3后才提供
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // 第三个参数传的this,处理器就是自己嘛   到此一个代理对象就此new出来啦
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
  }
  // 找找看看接口里有没有自己定义equals方法和hashCode方法,这个很重要  然后标记一下
  // 注意此处用的是getDeclaredMethods,只会找自己的
  private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
    for (Class<?> proxiedInterface : proxiedInterfaces) {
      Method[] methods = proxiedInterface.getDeclaredMethods();
      for (Method method : methods) {
        if (AopUtils.isEqualsMethod(method)) {
          this.equalsDefined = true;
        }
        if (AopUtils.isHashCodeMethod(method)) {
          this.hashCodeDefined = true;
        }
        // 小技巧:两个都找到了 就没必要继续循环勒
        if (this.equalsDefined && this.hashCodeDefined) {
          return;
        }
      }
    }
  }
   // 对于这部分代码和采用CGLIB的大部分逻辑都是一样的,Spring对此的解释很有意思:
   // 本来是可以抽取出来的,使得代码看起来更优雅。但是因为此会带来10%得性能损耗,所以Spring最终采用了粘贴复制的方式各用一份
   // Spring说它提供了基础的套件,来保证两个的执行行为是一致的。
   //proxy:指的是我们所代理的那个真实的对象;method:指的是我们所代理的那个真实对象的某个方法的Method对象args:指的是调用那个真实对象方法的参数。
  // 此处重点分析一下此方法,这样在CGLIB的时候,就可以一带而过了~~~因为大致逻辑是一样的
  @Override
  @Nullable
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 它是org.aopalliance.intercept这个包下的  AOP联盟得标准接口
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;
    // 进入invoke方法后,最终操作的是targetSource对象
    // 因为InvocationHandler持久的就是targetSource,最终通过getTarget拿到目标对象
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;
    try {
      //“通常情况”Spring AOP不会对equals、hashCode方法进行拦截增强,所以此处做了处理
      // equalsDefined为false(表示自己没有定义过eequals方法)  那就交给代理去比较
      // hashCode同理,只要你自己没有实现过此方法,那就交给代理吧
      // 需要注意的是:这里统一指的是,如果接口上有此方法,但是你自己并没有实现equals和hashCode方法,那就走AOP这里的实现
      // 如国接口上没有定义此方法,只是实现类里自己@Override了HashCode,那是无效的,就是普通执行吧
      if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
        return equals(args[0]);
      }
      else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
        return hashCode();
      }
      // 下面两段做了很有意思的处理:DecoratingProxy的方法和Advised接口的方法  都是是最终调用了config,也就是this.advised去执行的~~~~
      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;
      //是否暴露代理对象,默认false可配置为true,如果暴露就意味着允许在线程内共享代理对象,
      //注意这是在线程内,也就是说同一线程的任意地方都能通过AopContext获取该代理对象,这应该算是比较高级一点的用法了。
      // 这里缓存一份代理对象在oldProxy里~~~后面有用
      if (this.advised.exposeProxy) {
        oldProxy = AopContext.setCurrentProxy(proxy);
        setProxyContext = true;
      }
      //通过目标源获取目标对象 (此处Spring建议获取目标对象靠后获取  而不是放在上面) 
      target = targetSource.getTarget();
      Class<?> targetClass = (target != null ? target.getClass() : null);
      // 获取作用在这个方法上的所有拦截器链~~~  参见DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice方法
      // 会根据切点表达式去匹配这个方法。因此其实每个方法都会进入这里,只是有很多方法得chain事Empty而已
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      if (chain.isEmpty()) {
        // 若拦截器为空,那就直接调用目标方法了
        // 对参数进行适配:主要处理一些数组类型的参数,看是表示一个参数  还是表示多个参数(可变参数最终到此都是数组类型,所以最好是需要一次适配)
        Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
        // 这句代码的意思是直接调用目标方法~~~
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {
        // 创建一个invocation ,此处为ReflectiveMethodInvocation  最终是通过它,去执行前置加强、后置加强等等逻辑
        invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        // 此处会执行所有的拦截器链  交给AOP联盟的MethodInvocation去处理。当然实现还是我们Spring得ReflectiveMethodInvocation
        retVal = invocation.proceed();
      }
      // 获取返回值的类型
      Class<?> returnType = method.getReturnType();
      if (retVal != null && retVal == target &&
          returnType != Object.class && returnType.isInstance(proxy) &&
          !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
         // 一些列的判断条件,如果返回值不为空,且为目标对象的话,就直接将目标对象赋值给retVal
        retVal = proxy;
      }
      // 返回null,并且还不是Void类型。。。抛错
      else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
        throw new AopInvocationException(
            "Null return value from advice does not match primitive return type for: " + method);
      }
      return retVal;
    }
    finally {
      // 释放~~
      if (target != null && !targetSource.isStatic()) {
        targetSource.releaseTarget(target);
      }
      // 把老的代理对象重新set进去~~~
      if (setProxyContext) {
        AopContext.setCurrentProxy(oldProxy);
      }
    }
  }
  // AOP帮我们实现的CgLib方法
  @Override
  public boolean equals(@Nullable Object other) {
    if (other == this) {
      return true;
    }
    if (other == null) {
      return false;
    }
    JdkDynamicAopProxy otherProxy;
    if (other instanceof JdkDynamicAopProxy) {
      otherProxy = (JdkDynamicAopProxy) other;
    }
    else if (Proxy.isProxyClass(other.getClass())) {
      InvocationHandler ih = Proxy.getInvocationHandler(other);
      if (!(ih instanceof JdkDynamicAopProxy)) {
        return false;
      }
      otherProxy = (JdkDynamicAopProxy) ih;
    }
    else {
      // Not a valid comparison...
      return false;
    }
    // If we get here, otherProxy is the other AopProxy.
    return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised);
  }
  // AOP帮我们实现的HashCode方法
  @Override
  public int hashCode() {
    return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
  }
}


细节:


  • 除了实现类里自己写的方法(接口上没有的),其余方法统一都会进入代理得invoke()方法里面。只是invoke上做了很多特殊处理,比如DecoratingProxy和Advised等等的方法,都是直接执行了。
  • object的方法中,toString()方法会被增强(至于为何,我至今还没找到原因,麻烦的知道的给个答案) 因为我始终不知道AdvisedSupport#methodCache这个字段事什么把toString()方法缓存上的,打断点都没跟踪上
  • 生成出来的代理对象,Spring默认都给你实现了接口:SpringProxy、DecoratingProxy、Advised- 说明:CGLIB代理出来的对象没有实现接口DecoratingProxy。(多谢评论区【神的力量】小伙伴的指正)



相关文章
|
7天前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
|
2月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
77 1
|
29天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
16天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
65 8
|
2月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
99 5
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
92 8
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
54 5
|
2月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
60 4