通过源码来理解Cglib与JDK动态代理

news2024/12/26 13:30:10

最近在阅读到了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动态代理

标准的用法

public interface TestInterface { void sayHello(); } public static class Test implements TestInterface { @Override public void sayHello() { System.out.println("hello"); } } //需要定义一个实现了InvocationHandler接口的对象,目的是获取被代理类的实例和自定义增强方法 public static class MyInvocationHandler implements InvocationHandler{ /
/被代理类的实例对象 protected Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("增强方法"); 
//调用被代理类的实例对象通过反射执行目标方法 Object result = method.invoke(target, args); return result; } } public static void main(String[] args) { Test test = new Test(); TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test)); testInterface.sayHello(); } 复制代码

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

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

逻辑图

Cglib动态代理

标准的用法

public static class Test implements TestInterface { @Override public void sayHello() { System.out.println("hello"); } } //实现MethodInterceptor接口注册回调函数对代理类中所有方法进行拦截增强 public static class MyInvocationHandler implements MethodInterceptor { //o为继承了被代理类的代理类对象,method为执行方法,objects为方法参数 //methodProxy为代理对象方法,其中有被代理方法和代理方法的映射关系 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("增强方法"); return methodProxy.invokeSuper(o,objects); } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Test.class); enhancer.setCallback(new MyInvocationHandler()); TestInterface testInterface = (TestInterface)enhancer.create(); testInterface.sayHello(); } 复制代码

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

//Cglib中都是通过代理类中的方法来替换被代理类,然后直接调用代理类对象的方法即可 public static class TestProxy extends Test{ public final void sayHello() { System.out.println("增强方法"); super.sayHello(); } } 复制代码

逻辑图

​==============================================================

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

Spring源码验证

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

//Spring中Enhancer对象注册的三种拦截器 //回调数组,根据CALLBACK_FILTER中accept方法返回的索引值去从该数组中选择对应的Callback private static final 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的效果。 两种代理方式一个通过继承获取类方法信息,一种通过接口获取类方法信息,在理解其原理后,如何选型使用还是看业务场景和两种方式的执行效率来决定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/54618.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

纳尼?华为首席架构师只用434页笔记,就将网络协议给拿下了

不管是前端还是后端&#xff0c;几乎所有的程序运行都会涉及到网络协议。10 个程序员里面&#xff0c;10 个都说自己学过网络协议&#xff0c;9 个说自己懂网络协议。但真正面试的时候&#xff0c;能回答出相关问题的&#xff0c;可能只有两三个。 金九银十跳槽热季&#xff0…

七、【React-Router6】路由传参 之 search

文章目录1、routes.js2、Message.jsx3、Detail.jsx4、Result5、另外一个可以获取 search 的新 Hook &#xff1a;useLocation项目修改自 上一节 的 Demo 1、routes.js import { Navigate } from react-router-dom import About from ../components/About import Home from ../…

【D3.js】1.18-给 D3 标签添加样式

title: 【D3.js】1.18-给 D3 标签添加样式 date: 2022-12-02 14:44 tags: [JavaScript,CSS,HTML,D3.js,SVG] 标签也可以添加样式。 一、学习目标 如何设置字体大小&#xff1f; .attr(“font-size”,25) 如何填充颜色&#xff1f; .attr(“fill”,“red”) 二、题目 将 text 元…

LeetCode简单题之不同的平均值数目

题目 给你一个下标从 0 开始长度为 偶数 的整数数组 nums 。 只要 nums 不是 空数组&#xff0c;你就重复执行以下步骤&#xff1a; 找到 nums 中的最小值&#xff0c;并删除它。 找到 nums 中的最大值&#xff0c;并删除它。 计算删除两数的平均值。 两数 a 和 b 的 平均值…

[操作系统笔记]连续分配管理方式

内容系听课复习所做笔记&#xff0c;图例多来自课程截图 连续分配管理方式 连续分配&#xff1a;指为用户进程分配的必须是一个连续的内存空间 相应地&#xff0c;非连续分配可以是离散的 对于固定分区分配&#xff0c;需要有一个分区说明表&#xff0c;类似下表&#xff1a; …

【jmeter录制浏览器上特定的单个请求】

目录准备工作jmeter代理设置设置postman代理复制浏览器是特定的url背景&#xff1a;想要对浏览器某一个请求做测试&#xff0c;直接手动输入到jmeter不切实际&#xff0c;一般是使用jmeter代理的方式录制下来&#xff0c;但会有个问题&#xff0c;一般浏览器加载许多其他请求&a…

在虚拟机中安装Linux操作系统详细步骤

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 在虚拟机中安装Linux操作系统详细步骤专栏&#xff1a;《Linux从小白到大神》| 系统学习Linux开发、VI…

达梦数据库表空间误删恢复实操

达梦数据库表空间误删恢复实操1.表空间失效文件检查2.表空间失效文件恢复准备3.表空间失效文件恢复4.表空间失效文件恢复实操1.表空间失效文件检查 表空间恢复失效文件的检查。 语法格式 SP_FILE_SYS_CHECK ();语句功能 在 LINUX 操作系统下&#xff0c;检查是否有数据文件被…

一个已经存在10年,却被严重低估的 Python 库

今天介绍的是一个已经存在十年&#xff0c;但是依旧不红的库 decorator&#xff0c;好像很少有人知道他的存在一样。 这个库可以帮你做什么呢 &#xff1f; 其实很简单&#xff0c;就是可以帮你更方便地写python装饰器代码&#xff0c;更重要的是&#xff0c;它让 Python 中被…

代码随想录刷题Day52 | 300. 最长递增子序列 | 674. 最长连续递增序列 | 718. 最长重复子数组

代码随想录刷题Day52 | 300. 最长递增子序列 | 674. 最长连续递增序列 | 718. 最长重复子数组 300. 最长递增子序列 题目&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或…

2022年NPDP新版教材知识集锦--【第四章节】(6)

《产品经理认证(NPDP)知识体系指南(第2版)》已于2022年4月正式上架发行&#xff0c;新版教材自2022年11月NPDP考试起使用。将新版NPDP教材中的相关知识点进行了整理汇总&#xff0c;包括详细设计与规格阶段相关内容&#xff0c;快来看看吧。 【制造与装配阶段】(全部获取文末) …

SAP-ABAP-企业微信:ZCSM37-后台JOB异常检查主动推送企业微信群

场景&#xff1a;异常JOB主动推送企业微信群 &#xff08;企业微信机器人可百度创建&#xff0c;注意群不能有企业外的人员&#xff0c;否则接口无效&#xff09; 事务代码&#xff1a;ZSM37_CHECK程序名称&#xff1a;ZSM37_CHECK程序目的&#xff1a; ZCSM37-后台JOB异常检…

Java+JSP+MySQL基于SSM的物流公司物流订单管理系统-计算机毕业设计

项目介绍 随着我国经济的高速增长&#xff0c;物流快递的数量也在不断的增加&#xff0c;同时面临的就是如何更加方便快捷和高效的管理物流订单的问题&#xff0c;传统模式的物流订单管理模式明显已经不能够满足当下的需求&#xff0c;于是我们提出了基于B/S的贴心物流公司物流…

Redis未授权漏洞利用

1、背景介绍 近期公司内部安装主机安全组件&#xff0c;检测出一些安全漏洞&#xff0c;其中就有利用redis未授权漏洞进行攻击。 2、攻击原理 正常redis默认情况下&#xff0c;会绑定在0.0.0.0:6379&#xff0c;如果没有限制来源IP并且甚至没有密码&#xff0c;那么就会导致…

举个栗子~Minitab 技巧(5):掌握常用快捷键,提高统计分析效率

在日常使用 Minitab 时&#xff0c;大部分小伙伴的习惯是使用鼠标进行点击和拖拽等操作。然而&#xff0c;在使用频率很高的情况下&#xff0c;这种方式会带来很多重复且低效的劳动。 其实&#xff0c;Minitab 软件内置了许多快捷键&#xff0c;可以快速实现新建、打开、保存、…

虹科分析 | 终端安全 | 移动目标防御是“变革性”技术——GARTNER

使用前Gartner连续第二年将移动目标防御&#xff08;MTD&#xff09;作为特色技术&#xff0c;并将Morphisec作为该技术的样本供应商&#xff0c;在其报告《新兴技术影响雷达&#xff1a;安全》中。作者将MTD定义为“…一种技术趋势&#xff0c;其中动态或静态排列变形、转换或…

sentinel中流控规则 并发线程数的实战理解

先看下官网文档关于并发线程数的解释&#xff1a;链接地址 public class FlowThreadDemo {private static AtomicInteger pass new AtomicInteger();private static AtomicInteger block new AtomicInteger();private static AtomicInteger total new AtomicInteger();priva…

linux_mysql安装教程带安装包(亲测有效)

文章目录1.检查当前系统是否安装mysql2.上传mysql安装包/opt/software目录下3.解压安装包4.在安装目录下执行rpm安装5.删除/etc/my.cnf文件中datadir指向的目录下所有内容&#xff0c;6.初始化数据库7.查看临时生成的root用户密码8.启动mysql服务9.登陆mysql数据库10.修改root用…

Fiddler导出JMeter脚本插件原理

目录 一、Fiddler导出JMeter脚本插件原理 二、Fiddler导出JMeter脚本插件的基本使用 结语 一、Fiddler导出JMeter脚本插件原理 既然JMeter本质上是一个xml文档&#xff0c;Fiddler可以抓取HTTP请求包&#xff0c;插件的基本原理就是Fiddler抓取HTTP请求包将HTTP请求信息通过…

【电商项目实战】修改密码(详细篇)

&#x1f341;博客主页&#xff1a;&#x1f449;不会压弯的小飞侠 ✨欢迎关注&#xff1a;&#x1f449;点赞&#x1f44d;收藏⭐留言✒ ✨系列专栏&#xff1a;&#x1f449;SpringBoot电商项目实战 ✨学习社区&#xff1a; &#x1f449;不会压弯的小飞侠 ✨知足上进&#x…