【小家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。(多谢评论区【神的力量】小伙伴的指正)



相关文章
|
12天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
25 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
2天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
8 1
|
3天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
11 1
|
27天前
|
缓存 JavaScript Java
Spring之FactoryBean的处理底层源码分析
本文介绍了Spring框架中FactoryBean的重要作用及其使用方法。通过一个简单的示例展示了如何通过FactoryBean返回一个User对象,并解释了在调用`getBean()`方法时,传入名称前添加`&`符号会改变返回对象类型的原因。进一步深入源码分析,详细说明了`getBean()`方法内部对FactoryBean的处理逻辑,解释了为何添加`&`符号会导致不同的行为。最后,通过具体代码片段展示了这一过程的关键步骤。
Spring之FactoryBean的处理底层源码分析
|
2月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
2月前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
3月前
|
缓存 安全 Java
Spring AOP 中两种代理类型的限制
【8月更文挑战第22天】
24 0
|
2月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
57 1
|
3天前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
16 5
|
21天前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
37 0