从根上理解Cglib与JDK动态代理

关于spring 类内部方法调用aop不生效原因,以及jdk,cglib 动态代理原理 引入:spring的aop 我们知道,在spring 的一个A类bean中方法fa调用A中的方法fb,即使fb上有则@Transactional,或者fb 被aop拦截了,此时fb上的所有代理织入操作都不会生效。而如果在另一个类B中调用A中的fb,则代理织入是生效的。 但是经过下面实验发现,spring完全可以实现类内部方法调用 代理增强依然生效。但为何spring 不这样做呢?网上没有找到原因,以下为自己的理解原因:Spring aop应该是故意这样做的 内部调用是调用自己,不然就没办法通过代理b.. 阅读详情

最近在阅读到了Spring源码对于两种动态代理使用在不同场景下的使用,两种方式各有利弊写一篇文加深自己的认识。文中对于源码的涉及较少,更多的是作者自己的理解和举例,然后通过部分源码验证。

首先看两个面试经常会遇到的关于Spring的问题:

  1. @Configuration和@Component注解的不同

  • @Configuration修饰的类会被Cglib动态代理,在类内部方法相互调用添加了@Bean注解的方法时通过在切面方法中调用getBean()方法来保证调用该方法返回的都是同一个实例

  • @Component修饰的类不会被代理,每次方法内部调用都会生成新的实例,这样就不能保证其生成的对象是一个单例对象。

  1. @Transactional失效的原因

  • @Transactional可以JDK或Cglib动态代理实现的事务(默认JDK),在Bean创建时如果检测到类中有@Transactional就会对其进行动态代理,如果类内部没有被@Transactional修饰的方法中调用了其它被@Transactional修饰的内部方法,那么此时事务注解是不会生效的,原因在于只有外部调用才会走代理增强逻辑而内部类的互相调用只是原对象的方法调用,没有经过代理类。

其实上面可以看出出Spring在使用两种代理方式时的不同处理:@Configuration修饰的类被Cglib动态代理后,类内部方法调用也可以走增强逻辑,而含有@Transactional注解的类无论是Cglib还是JDK动态代理都不能进行方法内部的相互调用。

两种代理方式的调用逻辑
JDK动态代理

标准的用法

publicinterfaceTestInterface{
    voidsayHello();
}

publicstaticclassTestimplementsTestInterface{
    @OverridepublicvoidsayHello(){
        System.out.println("hello");
    }
}

//需要定义一个实现了InvocationHandler接口的对象,目的是获取被代理类的实例和自定义增强方法publicstaticclassMyInvocationHandlerimplementsInvocationHandler{
    //被代理类的实例对象protected Object target;
    publicMyInvocationHandler(Object target){
        this.target = target;
    }
    @Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        System.out.println("增强方法");
        //调用被代理类的实例对象通过反射执行目标方法
        Object result = method.invoke(target, args);
        return result;
    }
}

publicstaticvoidmain(String[] args){
    Test test = new Test();
    TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test));
    testInterface.sayHello();
}

下面是代理类的逻辑代码,这个代理类并不是用反编译内存中的代理类来获取得,是作者自己整了一个类似的,如果要获取真正的代理类代码网上方法很多

//代理类的父类,里面有生成代理类的主要逻辑publicstaticclassProxy{
    //被代理对象实例的调用对象protected InvocationHandler h;
}
//生成的代理类继承Proxy主要是为了使用父类中的InvocationHandler对象来调用被代理类对象的目标方法//实现共同接口是为了获取需要增强的目标方法publicstaticclassTestProxyextendsProxyimplementsTestInterface{
    protectedTestProxy(InvocationHandler h){
        super(h);
    }
    @OverridepublicvoidsayHello(){
        try {
            //这里对获取接口方法做了简化处理//调用父类中存储的被代理对象的handler执行代理逻辑super.h.invoke(this, this.getClass().getInterfaces()[0].getMethods()[0],null);
        } catch (Throwable e) {
            thrownew RuntimeException(e);
        }
    }
}

逻辑图

Cglib动态代理

标准的用法

publicstaticclassTestimplementsTestInterface{
    @OverridepublicvoidsayHello(){
        System.out.println("hello");
    }
}

//实现MethodInterceptor接口注册回调函数对代理类中所有方法进行拦截增强publicstaticclassMyInvocationHandlerimplementsMethodInterceptor{

    //o为继承了被代理类的代理类对象,method为执行方法,objects为方法参数//methodProxy为代理对象方法,其中有被代理方法和代理方法的映射关系@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)throws Throwable {
        System.out.println("增强方法");
        //invokeSuper方法传入的对象必须是代理类的实例对象//invoke方法则可以穿入被代理类的实例对象,通过被代理类实例调用方法return methodProxy.invokeSuper(o,objects);
    }
}

publicstaticvoidmain(String[] args){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Test.class);
    enhancer.setCallback(new MyInvocationHandler());
    TestInterface testInterface = (TestInterface)enhancer.create();
    testInterface.sayHello();
}

动态生成代理类的伪代码,省略了很多很多细节

//Cglib中都是通过代理类中的方法来替换被代理类,然后直接调用代理类对象的方法即可publicstaticclassTestProxyextendsTest{
    publicfinalvoidsayHello(){
        System.out.println("增强方法");
        super.sayHello();
    }
}

逻辑图

==============================================================

从上面可以看出Cglib和JDK最大的区别在于Cglib实现的动态代理并没有被代理类的实例对象,所有的方法调用都是通过代理类来实现的(子类方法 -> 增强逻辑 -> 子类代理方法 -> 父类方法),而JDK则同时生成了被代理类和代理类的实例对象,然后在代理类中保存有被代理类的引用,目标方法的调用还是被代理对象执行的。Cglib方法调用时是使用代理类对象内部方法的相互调用实现的,由于代理类的所有方法都进行了改写,所以内部调用也会被增强,JDK方法调用时是代理类对象和被代理类对象间方法的相互调用实现的,只有通过调用代理类对象的代理方法时才会走增强逻辑,而如果是被代理对象自己的内部调用,被代理对象方法没有改变,所以无法增强。 理解了这一点再看Spring动态代理的使用就好理解了

Spring源码验证

调用@Configuration注解的类时会用到的代理类拦截器

//Spring中Enhancer对象注册的三种拦截器//回调数组,根据CALLBACK_FILTER中accept方法返回的索引值去从该数组中选择对应的Callbackprivatestaticfinal Callback[] CALLBACKS = new Callback[] {
      new BeanMethodInterceptor(),
      new BeanFactoryAwareMethodInterceptor(),
      NoOp.INSTANCE
};

//BeanMethodInterceptor的intercept方法,对父类中所有带有@Bean注解的方法都进行拦截增强//无论是Spring通过反射实例化Bean还是配置类中方法的内部调用,都会通过BeanFactory来生成和获取Bean实例public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
         MethodProxy cglibMethodProxy)throws Throwable {
   //是否为Spring通过反射调用 if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
      //调用父类方法生成新的Bean对象,并将其注册到Spring上下文中return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
   }

   //类方法的内部调用,从BeanFactory中获取bean//即使通过内部方法直接调用为能保证获取的对象为同一实例return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}

Cglib对于@Transactional注解采用的代理类拦截器DynamicAdvisedInterceptor

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
   Class<?> targetClass = null;
   Object target = null;
   try {
      if (this.advised.exposeProxy) {
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
      //Spring缓存了被代理类的实例//获取被代理类实例
      target = getTarget();
      if (target != null) {
         targetClass = target.getClass();
      }
      //获取目标方法的拦截器链,被@Transactional修饰的方法会有缓存方法和调用链关系
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      Object retVal;
      if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         //没有被@Transactional修饰的方法会直接调用被代理类本身来执行//此处和Cglib通用的处理不一样,Spring缓存和被代理实例,用被代理类实例来执行方法//所以未被注解修饰的方法调用注解修饰的方法不能触发拦截器
         retVal = methodProxy.invoke(target, argsToUse);
      }
      else {
         //@Transactional修饰的方法会通过代理类对象来执行,进入拦截器执行增强逻辑
         retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
      }
      retVal = processReturnType(proxy, target, method, retVal);
      return retVal;
   }
   finally {
      if (target != null) {
         releaseTarget(target);
      }
      if (setProxyContext) {
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

JDK的处理逻辑同样是调用被代理类来执行未加@Transactional注解的方法,就不多写了。

小结

Cglib动态代理与JDK动态代理的区别本质上应该对于代理对象的调用方式有差别,Cglib是直接将代理类对象作为目标对象使用,增强逻辑直接写入代理类的子类方法中,调用方法时只需一个代理类对象即可,而JDK则是将被代理类对象引用存放在代理类对象中,增强逻辑在代理对象中存放而实际执行方法还需要调用被代理对象。当然Cglib通过缓存被代理类的实例对象也可以做到JDK的效果。

两种代理方式一个通过继承获取类方法信息,一种通过接口获取类方法信息,在理解其原理后,如何选型使用还是看业务场景和两种方式的执行效率来决定。

深入理解动态代理:JDK动态代理与CGLIB动态代理 动态代理是一种在运行时创建代理对象的技术,通过代理对象来控制对原对象的访问,能够在不修改原对象的情况下添加额外的功能。JDK动态代理:基于Java反射机制,只能代理实现了接口的类。CGLIB动态代理:基于字节码生成技术,可以代理没有实现接口的类。// 抽象主题接口动态代理在Java中提供了一种灵活而强大的机制,能够在运行时为目标对象添加额外的功能。JDK动态代理和CGLIB动态代理各有优势和适用场景,开发者可以根据具体需求选择合适的代理机制。希望这篇文章对你理解动态代理有所帮助。 阅读详情

相关推荐

电蚊拍为什么能秒杀蚊子?深入解析两种四倍压整流电路设计差异

本文深入解析电蚊拍秒杀蚊子的核心原理——四倍压整流电路设计,对比对称式与级联式两种方案的性能差异。通过详细电路分析、实测数据对比和选型建议,揭示高压电路设计的关键技术要点,为电子爱好者和工程师提供实用参考。

rust6ferris的博客 748

基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。...

基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Trasactional注解的事务管理,但在通过基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务是有区别的,我们接下来看看到底有哪些区别。       一、基础...

lvwenwen 358

DC/DC开关电源EMC仿真红宝书

本红宝书系统性介绍开关电源EMC仿真方法,包括仿真原理、仿真思路、仿真工具以及仿真效率提升与仿测一致性的实战经验。从理论到仿真,从仿真到应用,真正帮你实现一文掌握开关电源EMC仿真!全文约14000字,适读人群:开关电源设计、电磁兼容EMC相关从业者,电磁相关专业研究生及CST仿真爱好者等。

电磁学社 2498

动态代理之spring事务调用本类方法不生效问题

1.先看示例代码 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } @Controller @RequestMapping("acontroller") public class AController { @Autowired

houjx3的博客 913

在centos7部署k8s搭建kubernetes集群

Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。 Kubernetes一个核心的特点就是能够自主的管理容器来保证云平台中的容器按照用户的期望状态运行着(比如用户想让apache一直运行,用户不需要关心怎么去做,Kubernetes会自动去监控,然后去重启,新建,总之,让apache一直提供服务),管理员可以加载一个微型服务,让规划器来找

山东梅长苏 1万+

Oracle PLM Agile936 单点登录配置方式

Oracle PLM Agile936 单点登录配置方式

Jay的个人博客 824

Spring AOP动态代理(JDK与CGLib)及类内部方法事务不生效原因解析

1.创建接口 public interface UserService { // 模拟操作 boolean getById(String id); } 2.创建实现类 public class UserServiceImpl implements UserService { // 实现接口方法, 并返回结果 public boolean getById(String id) ...

跟心爱的人一起浪迹天涯 1675

54、生物神经网络建模全解析

本文系统解析了生物神经网络的建模方法,涵盖从单个神经元到神经网络的多个时空尺度。首先介绍神经元层面的生物物理模型(如霍奇金-赫胥黎模型和多隔室模型)与多种简化模型(如菲茨休-纳古莫、莫里斯-勒卡尔、积分发放模型等),并分析其适用场景。随后探讨突触层面的信号传递机制,对比电突触与化学突触在结构、传输速度和方向上的差异,阐述其在神经网络建模中的理论意义。文章旨在为理解神经系统的信息处理机制、构建受生物启发的人工智能模型提供理论基础。

app77的博客 76

通俗易懂 快速理解 JDK动态代理 和 cglib动态代理

动态代理有两种: ① jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,类名为$proxy+数字 (例如:$proxy6) ② cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下

weixin_43935152的博客 1万+

【教程4>第8章>第44节】本章整体思维导图与学习总结

本教程系列提供FPGA/MATLAB/Simulink学习资源,包含《MATLAB入门100例》、《FPGA入门100例》、《Simulink入门60例》等基础教程,以及《FPGA/MATLAB/Simulink联合开发》进阶教程和《人工智能基础应用研究》专题,适合不同层次的学习者系统掌握相关技术。

FPGA/MATLAB学习教程/源码/项目合作开发 448

Android Framework SurfaceFlinger面试题及参考答案

请简述 SurfaceFlinger 在 Android 系统中的作用。 SurfaceFlinger 是 Android 系统中一个非常关键的服务。它主要负责将多个应用程序的 Surface 合成到一个屏幕显示内容中。 从应用程序的角度来看,每个应用都有自己的 UI 界面,这些 UI 界面实际上是由一系列的图层(Surface)构成。例如,一个应用可能有背景图层、按钮图层、文本图层等。应用通过向 SurfaceFlinger 提供这些 Surface 来请求显示。 SurfaceFl

大模型大数据攻城狮的专栏 1183

Java两种动态代理JDK动态代理和CGLIB动态代理

目录 代理模式 JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP...

主要作为个人的笔记用,Not For Everyone。 14万+

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

实例理解JDK动态代理和Cglib动态代理及其区别

实例理解JDK动态代理和Cglib动态代理及其区别 深入理解设计模式之代理模式 代理商代理化妆品生产理解JDK动态代理 代理商代理汽车制造理解Cglib动态代理

Duktig的博客 6063

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Java动态代理与CGLIB动态代理实现原理

动态代理是 AOP(Aspect Orient Programming)编程思想,理解动态代理原理,对学习AOP框架至关重要。 JDK动态代理不需要任何外部依赖,但是只能基于接口进行代理;CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法代理final对象与final方法。(final类型不能有子类,final方法不能被重载)

write6的专栏 2934

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

静态代理,JDK动态代理,Cglib动态代理详解

关注微信公众号【Java之言】,更多干货文章和学习资料,助你放弃编程之路! 文章目录一、代理模式二、静态代理三、动态代理 一、代理模式 代理模式(Proxy Pattern)是程序设计中的一种设计模式,他的特征是代理类和委托类实现有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。 代理类与委托类之间通常会存在关联关系,一个代理类对象与一个委托类对象(目标对象)关联,代理类对象本身并不真正实现服务,而是通过调用委托类对象的相关方法,来提供特定的服务。 .

陈皮的JavaLib 2848

Cglib和jdk动态代理的区别

Cglib和jdk动态代理的区别 动态代理解决了方法之间的紧耦合, IOC解决了类与类之间的紧耦合! Cglib和jdk动态代理的区别? 1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理 2、 Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理 什么时候用cglib什么时候用jdk动态代理? 1、目标对象生成了接口 默认用JDK动态代

qq_43842093的博客 6456

【Spring】Spring AOP底层原理:JDK动态代理和CGLIB动态代理

Spring AOP 的实现原理是基于动态代理和字节码操作的。而动态代理是基于反射机制的。 在编译时, Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件。在运行时, Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP的功能。

m0_65277261的博客 3709

JDK动态代理与CGLIB代理原理及区别详解

Java中主要有两种动态代理实现方式:JDK动态代理和CGLIB代理。JDK动态代理通过反射机制在运行时创建实现指定接口的代理对象,而CGLIB代理则通过字节码操作技术动态生成目标类的子类来实现代理功能。

小红的布丁的博客 1510

JDK动态代理、Cglib动态代理及Spring AOP

Java中的JDK动态代理是一种通过反射机制生成代理对象的技术,使得我们能够在运行时动态地创建某个接口的代理类,而无需手动编写实现类。JDK动态代理通常用于横切关注点(如日志、事务管理、安全性等)在方法调用前后进行处理,而不需要修改目标类的源代码。JDK 动态代理特点只能代理实现了接口的类或对象。在运行时动态生成代理类,避免了手动创建静态代理类的繁琐。使用简单,适用于代理接口的场景。应用场景:适合需要对接口进行动态代理的场景,如一些简单的业务逻辑。Cglib 动态代理特点。

m0_67391870的博客 1120

Spring的两种动态代理:Jdk和Cglib 的区别和实现

一、原理: java动态代理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。 cglib动态代理: 利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 JDK动态代理和CGLIB字节码生成的区别? JDK动态代理只能对实现了接口的类生成代理,而不能针对类 CGLIB是针对类实现代理,主要是对指定的类生成一...

日拱一卒 5294

JDK 动态代理和 CGLIB 动态代理

JDK 动态代理和 CGLIB 动态代理静态代理JDK 动态代理CGLIB 动态代理总结 我们在阅读一些 Java 框架的源码时,基本上常会看到使用动态代理机制,它可以无感的对既有代码进行方法的增强,使得代码拥有更好的拓展性。 通过从静态代理、JDK 动态代理、CGLIB 动态代理来进行本文的分析。 静态代理 静态代理就是在程序运行之前,代理类字节码.class就已编译好,通常一个静态代理类也只代...

@龙猫的博客 3711
上一篇: 堪称神级的SpringBoot,从基础入门到实战进阶
下一篇: 我人傻了,阿里面试怎么这么难!三面侥幸拿到offer,坎坷经历分享
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值