jdk与cglib动态代理及原理

 Spring的AOP在运行时多以jdk及cglib动态代理来实现。(作者jdk是1.8版本)

1 jdk 动态代理

Java中使用动态代理,只能对接口进行代理,不能对普通类进行代理。主要是由一个类及一个接口来实现:

InvocationHandler:调用处理器接口,我们需要实现该接口的invoke方法,用来完成代理工作。当代理实例在调用代理方法时,将会调用该接口的invoke方法。

Proxy: 提供了用于创建动态代理类和实例的静态方法。

public interface ShopInterface {void buy(String goodsInfo,Double price);ShopInterface showInfo(Object object);}public class People implements ShopInterface{@Overridepublic void buy(String goodsInfo, Double price) {System.out.println("购物,商品是:" + goodsInfo + ",价格是:" + price);}@Overridepublic ShopInterface showInfo(Object object) {System.out.println("信息展示:" + object);return this;}
}public class CustomInvocationHandler implements InvocationHandler {/*** 被代理的实例*/private final Object instance;public CustomInvocationHandler(Object instance) {this.instance = instance;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("jdk动态代理开始---");System.out.println("拦截的方法是:" + method.getName() + ",所属接口:" + method.getDeclaringClass().getName());if (args != null) {System.out.println("参数是:" + Arrays.asList(args));}Object result = method.invoke(instance, args);System.out.println("结果是:" + result);System.out.println("jdk动态代理结束---");return result;}public static void main(String[] args) {People people = new People();ShopInterface proxyInstance = (ShopInterface) Proxy.newProxyInstance(People.class.getClassLoader(), new Class<?>[]{ShopInterface.class}, new CustomInvocationHandler(people));proxyInstance.buy("iPhone",6999d);}
}

1.1 invocationHandler的invoke方法

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;

proxy: 代理实例。即Proxy的newProxyInstance方法创建的实例。

method: 被代理的方法

args:  方法参数

我们可以通过返回proxy来实现对某些被代理方法的连续调用:

public class ProxyReturnInvocationHandler implements InvocationHandler {private final Object instance;public ProxyReturnInvocationHandler(Object instance) {this.instance = instance;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理开始---");Object result = method.invoke(instance, args);System.out.println("代理结束---" + result);if ("showInfo".equals(method.getName())) return proxy;return result;}public static void main(String[] args) {People people = new People();ShopInterface proxyInstance = (ShopInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{ShopInterface.class}, new ProxyReturnInvocationHandler(people));proxyInstance.showInfo("黄先生").showInfo(27).showInfo("深圳");proxyInstance.buy("iphone",5999d);}}

1.2 可实现多接口

在调用Proxy到newProxyInstance方法时,可以传入多个接口类型。使用时把生成的代理实例转化为特定的接口类型即可:

public interface StudyInterface {void read(String book);}public class Student implements ShopInterface,StudyInterface {@Overridepublic void buy(String goodsInfo, Double price) {System.out.println("购买:" + goodsInfo + ",价钱:" + price);}@Overridepublic ShopInterface showInfo(Object object) {System.out.println("展示信息:" + object);return this;}@Overridepublic void read(String book) {System.out.println("看书:" + book);}}public class MoreInvocationHandler implements InvocationHandler {private final Object instance;public MoreInvocationHandler(Object instance) {this.instance = instance;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法定义来源:" + method.getDeclaringClass());Object result = method.invoke(instance, args);System.out.println("结束代理----" + result);if ("showInfo".equals(method.getName())) return proxy;return result;}public static void main(String[] args) {Student student = new Student();ShopInterface shopInterface = (ShopInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{ShopInterface.class, StudyInterface.class}, new MoreInvocationHandler(student));shopInterface.showInfo("学生").showInfo(18);StudyInterface studyInterface = (StudyInterface) shopInterface;studyInterface.read("Java从入门到放弃");}
}

1.3 原理

Proxy类的newProxyInstance方法,生成代理类实例。

图 newProxyInstance方法的部分截图

最后是在Proxy内部静态类ProxyClassFactory的apply方法生成代理类的字节码。

图 ProxyClassFactory的apply方法的部分截图

下面,我们将使用ProxyGenerator的generateProxyClass方法来生成代理类的class文件并对其进行反编译:

public class GenerateProxyClass {public static void main(String[] args) throws IOException {String proxyName = "com.article.CustomProxy"; //代理类名词byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, new Class<?>[]{StudyInterface.class, ShopInterface.class}, Modifier.FINAL);//final修饰符String pathStr = "/Users/huangzaizai/Desktop/temp/CustomPoxy.class"; // class输出位置Path path = Paths.get(pathStr);OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.WRITE);outputStream.write(bytes);outputStream.close();}}

生成的代理类class文件经过反编译后如下:

final class CustomProxy extends Proxy implements StudyInterface, ShopInterface {private static Method m1;private static Method m5;private static Method m3;private static Method m2;private static Method m4;private static Method m0;public CustomProxy(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final ShopInterface showInfo(Object var1) throws  {try {return (ShopInterface)super.h.invoke(this, m5, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void read(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void buy(String var1, Double var2) throws  {try {super.h.invoke(this, m4, new Object[]{var1, var2});} catch (RuntimeException | Error var4) {throw var4;} catch (Throwable var5) {throw new UndeclaredThrowableException(var5);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m5 = Class.forName("article.dynamic.ShopInterface").getMethod("showInfo", Class.forName("java.lang.Object"));m3 = Class.forName("article.dynamic.more.StudyInterface").getMethod("read", Class.forName("java.lang.String"));m2 = Class.forName("java.lang.Object").getMethod("toString");m4 = Class.forName("article.dynamic.ShopInterface").getMethod("buy", Class.forName("java.lang.String"), Class.forName("java.lang.Double"));m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

可以看到,生成的代理类继承了Proxy,而实现了传入的接口。并且在代理方法执行时,是通过调用InvocationHandler 实例的invoke方法,并把代理类的实例,被代理方法及参数传入了该方法。

1.3.1代理模式的本质

图 代理模式UML

jdk动态代理通过动态生成代理类本质上还是“静态代理”。

图 动态代理相关类

在运行时,动态生成CustomProxy动态代理类,继承于Proxy(所以不能代理类实例转化为目标类类型,只能转化成其接口类型。同时只能对接口进行代理而不是类),在调用代理类方法时,会调用InvocationHandlerd的invoke方法。

2 cglib代理

cglib是一个开源库,也经常用于实现动态代理(Spring用得较多)。它不仅可以对接口进行代理,还可以对类进行代理。

其主要也是由一个类及一个接口来实现:

Enhancer:增强器。用于设置代理方法拦截器、拦截器的过滤器及生成代理类。

Callback(通常是MethodInterceptor): 代理方法拦截器,用于实现对代理方法的增强。

public class Employee {public void work() {System.out.println("努力工作");}public void work(String task) {System.out.println("工作任务是:" + task);}}public class CustomTest {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Employee.class);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("cglib动态代理开始");if (args != null) {System.out.println("参数是:" + Arrays.asList(args));}Object result = proxy.invokeSuper(obj, args);System.out.println("动态代理结束---" + result);return result;}});Employee employee = (Employee) enhancer.create();employee.work();employee.work("开发某个模块");}}

2.1 MethodInterceptor

代理方法“around advice”增强的回调。即在通过代理类调用方法时,会调用MethodInterceptor的方法来实现对目标方法的增强。

public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;

obj: 代理类实例,即增强对象。

method: 执行的方法;

args:方法参数;

proxy: 目标方法的代理

MethodProxy类有两个方法用来执行目标方法:invoke和invokeSuper。

图MethodProxy的 invoke和invokeSuper方法

这两处方法唯一的不同是红圈处,即不同的f实例执行了invoke方法。而这两个实例分别是:

FastClass f1: 目标类的FastClass

FastClass f2: 代理类的FastClass

FastClass 是cglib用于快速寻找类的方法的一种机制类。对一个类的方法建立索引,通过索引来直接调用相应的方法,避免反射调用,提高效率。

图 MethoProxy 构造器断点调试

2.2 Enhancer

通过生成直接继承于目标类的子类来实现动态代理,但是不能代理final方法。

该类的setCallbacks用于设置一组方法拦截器。而setCallbackFilter则是对这些拦截器进行过滤选择。主要,在任何情况下,代理类只能调用一个Callback,所以如果设置了多个Callback,则必须设置CallbackFilter。

public class MethodInterceptor1 implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("拦截器1");Object result = proxy.invokeSuper(obj, args);System.out.println("结果是:" + result);return result;}
}public class MethodInterceptor2 implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("拦截器2");Object result = proxy.invokeSuper(obj, args);System.out.println("结果是:" + result);return result;}
}public class CustomTest2 {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Employee.class);enhancer.setCallbacks(new Callback[]{new MethodInterceptor1(),new MethodInterceptor2()});enhancer.setCallbackFilter(new CallbackFilter() {@Overridepublic int accept(Method method) {if (method.getParameterCount() > 0) return 1;return 0;}});Employee employee = (Employee) enhancer.create();employee.work();employee.work("开发某个模块");}
}

2.3 原理

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "文件输出位置"); 来生成cglib调试版本生成的代理类等相关类。

public class CustomTest3 {public static void main(String[] args) {// 设置 CGLIB 的 debug 输出位置System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/huangzaizai/Desktop/debugging");Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Employee.class);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("代理开始");proxy.invokeSuper(obj,args);System.out.println("代理结束");return null;}});Employee employee = (Employee) enhancer.create();employee.work();}
}

图 cglib生成的相关类

图 代理类继承了目标类

图 代理类中实现的父类(目标类)方法

当通过生成的代理实例调用方法时,会执行对MethodIntercaptor的intercept方法的调用。另外,代理类还生成了一个类似代理方法的方法:CGLIB$work$1。这个方法名在为“work”方法创建ProxyMethod时,作为proxyMethod的一个方法签名创建。

当在ProxyMethod中执行invokerSuper这个方法时,是根据这个签名的索引来查找方法。

3 jdk 动态代理与cglib的区别

jdk

cglib

生成类

只生成一个代理类。

生成类的数目较多,包括代理类,代理类和目标类(包括目标类的祖先)的FastClass类。而且代理类的代码量也更多。

可代理对象

只能是接口。

可以是接口也可以是是类,但是不能代理final方法。

实现方式

生成继承与Proxy并实例了代理接口的子类。

生成继承目标类的子类。

执行速度

大量依靠反射,执行效率会更慢些。

通过FastClass机制,根据方法索引来执行查找方法,避免了反射执行,速度上有提升,但是当需要代理的方法较多时,查找方法所耗费的时间也更多。同时在运行期间生成了这么多类,占据了大量的元空间内存。

表 jdk动态代理与cglib的对比

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

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

相关文章

Can‘t locate IPC/Cmd.pm in @INC (@INC contains:解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

AWTK 开源串口屏开发(4) - 采用数据

AWTK 开源串口屏开发 - 数据采集 1. 功能 数据采集是一个常用的功能&#xff0c;MCU 定时采集数据&#xff08;如环保设备定时采样空气中的污染物&#xff09;&#xff0c;并发送采样数据到串口屏&#xff0c;串口屏可以显示采样数据&#xff0c;也可以对采样数据进行管理&am…

实时数仓应用价值(下)

欢迎关注WX公众号&#xff1a;数据运营入表资产化服务 获取更多算法源码材料 2023数据资源入表白皮书&#xff0c;推荐系统源码下载-CSDN博客 浅析研发支出费用化和资本化的区别-CSDN博客 商业银行数据资产估值白皮书&#xff0c;推荐系统源码下载-CSDN博客 用友BIP数据资…

Visual Studio 配置DLL

我们在用Visual Studio进行开发时&#xff0c;如果没有正确配置DLL&#xff0c;就会出现类似“丢失***.dll”的错误。DLL配置有哪些方法&#xff1f; 1、手动复制 将dll文件拷贝到生成的.exe所在的文件夹里 2、配置环境 在右键属性->配置属性->调试->环境&#xf…

分布式技术之分布式消息队列通信

文章目录 什么是消息队列&#xff1f;消息队列的原理消息队列工作原理RocketMQ 消息队列原理及工作机制 什么是消息队列&#xff1f; 队列是一种具有先进先出特点的数据结构&#xff0c;消息队列是基于队列实现的&#xff0c;存储具有特定格式的消息数据&#xff0c;比如定义一…

scanf函数返回值被忽略

心怀希望的前进 前言 最近在复习c语言&#xff0c;发现了许多之前不了解的知识&#xff0c;今天想来与大家分享一下scanf返回值值被忽略的问题。 很多人应该都在vs中见到过&#xff0c;我们先说原因&#xff0c;再说改进方法 原因&#xff1a; scanf函数在读取数据时不会检…

【Week-P3】CNN天气识别

文章目录 一、环境配置二、准备数据三、搭建网络结构四、开始训练五、查看训练结果六、总结6.1 不改变学习率的前提下&#xff0c;将训练epoch分别增加到50、60、70、80、90&#xff08;1&#xff09;epoch 50 的训练情况如下&#xff1a;&#xff08;2&#xff09;epoch 60 …

nodejs微信小程序+python+PHP的艺术展览馆艺术品管理系统-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

自检服务器,无需服务器、不用编程。

自检服务器&#xff0c;无需服务器、不用编程。 大家好&#xff0c;我是JavaPub. 这几年自媒体原来热&#xff0c;很多人都知道了个人 IP 的重要性。连一个搞中医的朋友都要要做一个自己的网站&#xff0c;而且不想学编程、还不想花 RMB 租云服务。 老读者都知道&#xff0c…

main参数传递、反汇编、汇编混合编程

week03 一、main参数传递二、反汇编三、汇编混合编程 一、main参数传递 参考 http://www.cnblogs.com/rocedu/p/6766748.html#SECCLA 在Linux下完成“求命令行传入整数参数的和” 注意C中main: int main(int argc, char *argv[]), 字符串“12” 转为12&#xff0c;可以调用atoi…

直方图与均衡化

直方图 统计图像中相同像素点的数量。 使用cv2.calcHist(images, channels, mask, histSize, ranges)函数 images&#xff1a;原图像图像格式为uint8或float32&#xff0c;当传入函数时应用[]括起来&#xff0c;例如[img]。 channels&#xff1a;同样用中括号括起来&#xff…

准备用vscode代替sourceinsight

vscode版本1.85.1 有的符号&#xff0c;sourceinsight解析不到。 看网上说vscode内置了ripgrep&#xff0c;但ctrlshiftf在文件里查找的时候&#xff0c;速度特别慢&#xff0c;根本不像ripgrep的速度。ripgrep的速度是很快的。 但今天再查询&#xff0c;速度又很快了&#x…