Java设计模式之代理模式(二)

一、CGLIB动态代理

JDK动态代理要求被代理的类必须实现接口,有很强的局限性,而CGLIB动态代理则不要求被代理类实现接口。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。Cglib无法代理被final修饰的方法。

cglib的原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改。因为cglib是继承,重写方法,所以要求目标类不能是final的,方法也不能是final的。cglib的要求目标类比较宽松,只要能继承就可以了。cglib在很多的框架中使用,比如mybatis,spring框架中都有使用。

1、cglib动态代理的简单使用

使用CGLIB动态代理主要有以下几个步骤:

1.编写一个被代理类(也称委托类或目标类),无需实现任何接口;

2.自定义一个方法拦截器,实现MethodInterceptor接口,并重写intercept方法;

3.在intercept方法中调用MethodProxy类的invokeSuper方法(而不是调用invoke方法,因为invoke会引起死循环,导致堆栈内存溢出,具体原因在下面会详细分析。在invokeSuper方法中,底层实际上最终会调用被代理类中相应的被代理方法);

4.创建代理对象,并调用被代理类中的方法(实际上是调用代理对象中重写的代理方法)。

示例代码:

package com.tx.study.others.proxy.cglibProxy;/*** @Author: 倚天照海* @Description: 被代理类*/
public class InfoDemo {//被代理方法(被final修饰的方法无法被代理)public void welcome (String person){System.out.println("welcome :" + person);}}
package com.tx.study.others.proxy.cglibProxy;import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @Author: 倚天照海* @Description: 自定义方法拦截器,实现MethodInterceptor接口,并重写intercept方法*/
public class MyMethodInterceptor implements MethodInterceptor {/*** 重写intercept方法** @param o 代理对象* @param method 被代理方法对应的Method对象* @param objects 被代理方法的参数* @param methodProxy 方法代理对象* @return 被代理方法的返回值* @throws Throwable 可能会抛出的异常*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {//在真实的对象执行之前可以添加自己的操作,插入前置通知System.out.println("before method!!!");//在invokeSuper方法中最终会调用被代理类中对应的被代理方法Object value = methodProxy.invokeSuper(o, objects);//会引起死循环,导致内存溢出//Object value = methodProxy.invoke(o, objects);//在真实的对象执行之后可以添加自己的操作,插入后置通知System.out.println("after method!!!");return value;}}
package com.tx.study.others.proxy.cglibProxy;import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;/*** @Author: 倚天照海* @Description: cglib动态代理测试类*/
public class CglibProxyTest {/*** 该方法的作用就是封装获取代理对象的代码,即获取代理对象* @return 代理对象*/public static Object getProxyInstance() {//Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展Enhancer enhancer = new Enhancer();//为代理类设置父类,即代理类继承被代理类enhancer.setSuperclass(InfoDemo.class);//为代理类设置回调对象,即自定义的方法拦截器enhancer.setCallback(new MyMethodInterceptor());//创建代理对象return enhancer.create();}public static void main(String[] args) {//将代理类class文件存入本地磁盘,方便反编译查看源码String path = "D:\\ProgramFiles\\workspace\\zznode\\data-query\\tx-study\\src\\main\\java\\com\\tx\\study\\others\\proxy\\cglibProxy\\";System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);//获取代理对象InfoDemo instance = (InfoDemo) getProxyInstance();//调用被代理方法(底层会调用自定义方法拦截器中重写的intercept方法)instance.welcome("zhangsan");}}

运行结果:

before method!!!

welcome :zhangsan

after method!!!

2、cglib动态代理的原理

原理分析

执行测试类,会生成三个class文件,如下所示,通过反编译可以看到这三个文件中的源码。

InfoDemo$$EnhancerByCGLIB$$8b8da05b.class  (cglib生成的代理类,继承InfoDemo类)

InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class  (被代理类的FastClass,记为f1)

InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c.class  (代理类的FastClass,记为f2)(在下面会详细说明这三个类的部分源码)

在MethodProxy类中有一个静态内部类FastClassInfo,如下所示。FastClassInfo中有四个属性,FastClass f1表示被代理类的FastClass,FastClass f2表示代理类的FastClass,int i1表示被代理类的方法签名(实际上是方法对应的索引,根据该索引可以快速找到对应的方法,这就是cglib动态代理比JDK动态代理效率高的主要原因),int i2表示代理类的方法签名。通过FastClassInfo对象可以得到被代理类和代理类的FastClass,在下面会详细介绍。

private static class FastClassInfo {FastClass f1;//被代理类的FastClassFastClass f2;//代理类的FastClassint i1; //被代理类的方法签名(index)int i2;//代理类的方法签名private FastClassInfo() {}
}

先看一下本例中生成的代理类(InfoDemo$$EnhancerByCGLIB$$8b8da05b.class)的源码。

代理类InfoDemo$$EnhancerByCGLIB$$8b8da05b.class

public class InfoDemo$$EnhancerByCGLIB$$8b8da05b extends InfoDemo implements Factory {private MethodInterceptor CGLIB$CALLBACK_0;      //拦截器private static final Method CGLIB$welcome$0$Method;   //被代理方法(是Method对象)private static final MethodProxy CGLIB$welcome$0$Proxy; //方法代理(MethodProxy对象)//..........................................省略static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("CglibTest.InfoDemo$$EnhancerByCGLIB$$8b8da05b");Class var1;//通过反射获取被代理方法CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("CglibTest.InfoDemo")).getDeclaredMethods())[0];//生成与被代理方法对应的方法代理对象CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");//..........................................省略}//cglib会生成与被代理方法对应的代理方法CGLIB$welcome$0final void CGLIB$welcome$0(String var1) {//直接调用父类的被代理方法(实际上被代理方法就是在此处被调用的)super.welcome(var1);}//由于代理类继承了被代理类,所以在代理类中生成了被代理方法(进行了重写)public final void welcome(String var1) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if(this.CGLIB$CALLBACK_0 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}//判断代理类中是否设置了方法拦截,如果设置了就调用该拦截器的intercept方法//在本例中设置了拦截器enhancer.setCallback(new MyMethodInterceptor());if(var10000 != null) {var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);} else {//如果没有设置拦截器,就直接调用父类的被代理方法super.welcome(var1);}}//..........................................省略
}

在代理类中会生成与父类(即被代理类)中每一个方法相对应的两个方法,例如此例中父类的welcome方法,在代理类中生成了CGLIB$welcome$0代理方法和重写的welcome方法。另外,父类中的每一个方法都会在静态块中,通过MethodProxy.create生成对应的方法代理。

在本例测试类中,代理对象调用父类的被代理方法,即instance.welcome("zhangsan"),实际上调用的是代理类中被重写后的welcome方法,即public final void welcome(String var1)。在该方法中调用了拦截器MethodInterceptor的intercept方法。由于自定义的方法拦截器实现了MethodInterceptor接口,并对intercept方法进行了重写,所以,实际上调用的是重写后的intercept方法。在重写的intercept方法中调用了MethodProxy对象的invokeSuper(o, objects)方法,接下来重点看一下invokeSuper方法的执行过程,即分析MethodProxy的源码。

先看一下代理类中调用的intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy)方法,该方法中有四个参数,第一个参数是this,即调用代理类中重写后的welcome方法的对象,当然是代理对象了。第二个参数是被代理方法(实际上是Method对象),第三个参数是被代理方法的参数数组,第四个参数是方法代理对象(即MethodProxy对象)。这四个参数也就是自定义方法拦截器的intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)方法中的四个参数。所以,methodProxy.invokeSuper(o, objects)方法中的methodProxy就是方法代理对象,o就是代理对象,objects就是被代理方法的参数。

MethodProxy 源码分析

public class MethodProxy {//下面的前三个变量在create方法中,都已经得到了初始值了。private Signature sig1;private Signature sig2;private MethodProxy.CreateInfo createInfo;//在调用invoke或者invokeSuper中,通过init()方法生成FastClassInfoprivate volatile MethodProxy.FastClassInfo fastClassInfo;public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {this.init();MethodProxy.FastClassInfo fci = this.fastClassInfo;//调用代理类的FastClass的invoke方法return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException var4) {throw var4.getTargetException();}}public Object invoke(Object obj, Object[] args) throws Throwable {try {this.init();MethodProxy.FastClassInfo fci = this.fastClassInfo;//调用被代理类的FastClass的invoke方法return fci.f1.invoke(fci.i1, obj, args);} catch (InvocationTargetException var4) {throw var4.getTargetException();} catch (IllegalArgumentException var5) {if(this.fastClassInfo.i1 < 0) {throw new IllegalArgumentException("Protected method: " + this.sig1);} else {throw var5;}}}//init方法就是为了生成FastClassInfo,FastClassInfo中存放着两个fastclass(f1、f2)和两个方法索引的值(i1、i2)。private void init() {if(this.fastClassInfo == null) {Object var1 = this.initLock;synchronized(this.initLock) {if(this.fastClassInfo == null) {MethodProxy.CreateInfo ci = this.createInfo;MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();//不是每一个方法都生成一个fastclass,每一个方法的fastclass都是一样的,//只不过他们的i1,i2不一样。如果缓存中有就取出,没有就生成新的FastClassfci.f1 = helper(ci, ci.c1);fci.f2 = helper(ci, ci.c2);fci.i1 = fci.f1.getIndex(this.sig1);fci.i2 = fci.f2.getIndex(this.sig2);this.fastClassInfo = fci;this.createInfo = null;}}}}//根据一个类的信息,返回该对象的一个Fastclassprivate static FastClass helper(MethodProxy.CreateInfo ci, Class type) {Generator g = new Generator();g.setType(type);g.setClassLoader(ci.c2.getClassLoader());g.setNamingPolicy(ci.namingPolicy);g.setStrategy(ci.strategy);g.setAttemptLoad(ci.attemptLoad);return g.create();}//FastClassInfo是MethodProxy的静态内部类private static class FastClassInfo {FastClass f1;//被代理类的FastClassFastClass f2;//代理类的FastClassint i1; //被代理类的方法签名(index)int i2;//代理类的方法签名private FastClassInfo() {}}
}

在MethodProxy类的invokeSuper方法中调用了代理类的FastClass的invoke方法,接下来看一下代理类的FastClass的源码。代理类的FastClass的字节码反编译后的源码如下所示。

代理类的FastClass

代理类的FastClass(InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c)的字节码反编译后的源码:

public class InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c extends FastClass {public InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c(Class var1) {super(var1);}public int getIndex(Signature var1) {String var10000 = var1.toString();switch(var10000.hashCode()) {case -2055565910:if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {return 12;}break;case -1725733088:if(var10000.equals("getClass()Ljava/lang/Class;")) {return 24;}case 1013143764:if(var10000.equals("CGLIB$welcome$0(Ljava/lang/String;)V")) {return 17;}}//----省略}//在MethodProxy类的invokeSuper方法中调用了此处的invoke方法public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {8b8da05b var10000 = (8b8da05b)var2;int var10001 = var1;try {switch(var10001) {case 0:return var10000.toString();case 1:return new Integer(var10000.hashCode());case 17://调用代理对象的代理方法CGLIB$welcome$0var10000.CGLIB$welcome$0((String)var3[0]);}//----省略}}
}

由上述代码可知,在代理类的FastClass的invoke方法中调用了代理对象的CGLIB$welcome$0方法,由上面分析的代理类的源码可知,在代理类的CGLIB$welcome$0方法中直接调用父类的被代理方法,即调用此例中InfoDemo类的welcome方法,执行被代理方法。至此,cglib动态代理的大致过程就分析完毕了。

如果在自定义方法拦截器中调用的不是methodProxy.invokeSuper(o, objects)方法,而是methodProxy.invoke(o, objects)方法,为什么会引起死循环,导致内存溢出呢?由MethodProxy的源码可知,在MethodProxy的invoke方法中调用了被代理类的FastClass的invoke方法,所以,接下来看一下被代理类的FastClass(InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class)被反编译后的部分源码。

public class InfoDemo$$FastClassByCGLIB$$f4c7f3ac extends FastClass {
public Object invoke(Object obj, Object[] args) throws Throwable {try {switch(var10001) {case 0://调用被代理对象的被代理方法welcomevar10000.welcome((String)var3[0]);return null;case 1:}}
}
}

在被代理类的FastClass的invoke方法中调用被代理对象的welcome方法,通过代理对象调用被代理方法,与在main函数中instance.welcome(“zhangsan”)是一样的步骤,即又从头开始循环调用,直到栈内存溢出。

3、两种动态代理方式的对比

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

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

相关文章

git的学习之远程进行操作

1.代码托管GitHub&#xff1a;充当中央服务器仓库的角色 2.git远程进行操作 3.配置本地服务器的公钥 4.推送 5.git远程操作 pull .gitignore 6.给命令配置别名 git config --global alias.st status 7.标签管理 git tag -a [name] -m "XXX" [commit_id] 操作标签…

基于Python的自然语言处理系列(46):4-bit LLM 量化与 GPTQ

在本篇文章中&#xff0c;我们将深入探讨如何使用 GPTQ (Generative Pre-trained Quantization) 进行4-bit大语言模型(LLM)的量化。在大规模语言模型训练和推理的背景下&#xff0c;模型的量化不仅能够大大降低计算成本&#xff0c;还能够提高推理速度&#xff0c;因此对构建高…

查找与排序-插入排序

1.直接插入排序的基本思想 假设n个数据元素关键字存储在静态数组a中&#xff0c;则直接插入排序的基本思想可做如下描述&#xff1a; &#xff08;1&#xff09;初始有序子序列由一个元素a[0] 组成&#xff1b; &#xff08;2&#xff09;从a[1]开始&#xff0c;对于序列中每…

leetcode 75-13 k和数对的最大数目

我的思路 sort函数排序 然后双指针判断 这样时间复杂度nlgn 题解给出了一种空间换时间方法 用哈希表 注意一下写法 现在完全不会这样写 还有就是注意sort函数的代码 怎么写排序也给忘了 sort用的是什么排序方法

自由职业者的一天:作为小游戏开发者的真实工作日记

大家好&#xff0c;我是小蜗牛。 在这个快节奏的数字时代&#xff0c;自由职业者的生活往往充满了挑战与机遇。作为一名微信小游戏开发者&#xff0c;我的日常工作并不像人们想象中的那样充满光鲜亮丽的画面&#xff0c;而是由无数的编码、调试和创意碰撞组成的。今天&#xf…

MySQL 回收表碎片实践教程

前言&#xff1a; 在 MySQL 数据库中&#xff0c;随着数据的增删改操作&#xff0c;表空间可能会出现碎片化&#xff0c;这不仅会占用额外的存储空间&#xff0c;还可能降低表的扫描效率&#xff0c;特别是一些大表&#xff0c;在进行数据清理后会产生大量的碎片。本篇文章我们…

Lesson11---stack

Lesson11—stack cstack的介绍使用以及模拟实现 文章目录 Lesson11---stack前言一、stack成员函数1.stack2.empty3.size4. top5.push6.pop 二、stack相关题目1. 最小栈2.栈的压入、弹出序列 三、模拟实现总结 前言 stack的介绍和使用stack是一种容器适配器&#xff0c;专门用…

B+树(B树的改进)

目录 一、什么是B树&#xff1f; 二、B树的性质 1.B树被广泛作为数据库索引的索引结构 2.m个分支的结点有m个元素 3.每个元素对应子结点最大值 4.多级索引结构 5.叶子结点层包含所有元素 三、B树和B树的区别 四、B树的查找 1.顺序查找 2.随机查找 3.范围查找 一、什…

vue3完整Demo(数据绑定,数据显示,数据修改,数据提交)

需要引入的的依赖&#xff1a;jquery&#xff08;用于异步请求&#xff09; 一、数据显示的前端页面 条件查询数据并显示&#xff0c;下拉框使用的model双向绑定 二、js代码&#xff08;list页面的数据请求&#xff09; 后端传来的时间数据需要转换可以使用new Intl.DateTim…

Vue3 学习笔记(七)Vue3 语法-计算属性 computed详解

#1024程序员节|征文# 1、计算属性 computed 在 Vue.js 中&#xff0c;计算属性&#xff08;computed properties&#xff09;是一种特殊的响应式属性&#xff0c;它们根据依赖的响应式数据自动更新。计算属性非常适合用于当你需要根据现有数据派生出一些状态时。 (1)、基本用法…

LLM | 论文精读 | NeurIPS 2023 | SWIFTSAGE: 结合快思考与慢思考的生成智能体

论文标题&#xff1a;SWIFTSAGE: A Generative Agent with Fast and Slow Thinking for Complex Interactive Tasks 作者&#xff1a;Bill Yuchen Lin, Yicheng Fu, Karina Yang, Faeze Brahman, Shiyu Huang, Chandra Bhagavatula, Prithviraj Ammanabrolu, Yejin Choi, Xian…

【Vue3】第二篇

Vue3学习第二篇 01. 事件处理02. 事件传参03. 事件修饰符04. 数组变化侦测05. 计算属性06. class绑定07. style绑定08. 侦听器09. 表单输入绑定10. 模板引用 01. 事件处理 在vue当中的事件处理和html、css中的不一样&#xff0c;它单独做了处理。 注意&#xff1a;用法中只是用…

【Android】浅析OkHttp(1)

【Android】浅析OkHttp&#xff08;1&#xff09; OkHttp 是一个高效、轻量级的 HTTP 客户端库&#xff0c;主要用于 Android 和 Java 应用开发。它不仅支持同步和异步的 HTTP 请求&#xff0c;还支持许多高级功能&#xff0c;如连接池、透明的 GZIP 压缩、响应缓存、WebSocke…

JUC并发编程面试题总结

文章目录 1、创建线程的三种方式2、线程的状态3、线程的上下文切换4、run和start的区别5、sleep和wait区别6、虚假唤醒&#xff0c;精确唤醒7、两阶段终止模式8、多线程下的线程安全问题9、如何解决线程安全问题10、synchornized的原理11、锁升级的机制12、锁消除13、批量重偏向…

Unity编辑器制作多级下拉菜单

Unity编辑器下拉菜单 大家好&#xff0c;我是阿赵。   在Unity引擎里面编写工具插件&#xff0c;有时候会用到一些特殊的菜单形式&#xff0c;比如下拉选项。 通过下拉菜单&#xff0c;给用户选择不同的选项。   如果只是一层的下拉列表&#xff0c;可以用EditorGUILayout.…

Nginx upstream

什么是Nginx upstream&#xff1f; Nginx 模块一般分为三大类&#xff1a;handler、filter和upstream。 利用 handler、filter 这两个模块&#xff0c;可以使 Nginx 轻松完成任何单机工作。 upstream 模块将使 Nginx 跨越单机的限制&#xff0c;完成网络数据的接收、处理和转…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23目录1. Advancements in Visual Language Models for Remote Sensing: Datasets, Capabilities, and Enhancement Techniques摘…

Zig 语言通用代码生成器:逻辑,冒烟测试版发布二

Zig 语言通用代码生成器&#xff1a;逻辑&#xff0c;冒烟测试版发布二 Zig 语言是一种新的系统编程语言&#xff0c;其生态位类同与 C&#xff0c;是前一段时间大热的 rust 语言的竞品。它某种意义上的确非常像 rust&#xff0c;尤其是在开发过程中无穷无尽抛错的过程&#x…

高等数学-宋浩版2.0-映射

映射&#xff1a;X,Y为非空集合&#xff0c;存在法则F,对X(原像)中每个元素X&#xff0c;按法则F&#xff0c;在Y中有唯一元素与之对应&#xff0c;F为x到Y&#xff08;镜像&#xff09;的映射。f:X->Y X原像&#xff0c;Y像&#xff0c;x定义域&#xff0c;Df,Rf &#x…

python之多任务爬虫——线程、进程、协程的介绍与使用(16)

文章目录 1、什么是多任务?1.1 进程和线程的概念1.2 多线程与多进程的区别1.3 并发和并行2、python中的全局解释器锁3、多线程执行机制4、python中实现多线程(threading模块)4.1 模块介绍4.2 模块的使用5、python实现多进行程(Multiprocessing模块)5.1 导入模块5.2 模块的…