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.xdnf.cn/news/823222.html

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

相关文章

【并发设计模式】聊聊等待唤醒机制的规范实现

在多线程编程中&#xff0c;其实就是分工、协作、互斥。在很多场景中&#xff0c;比如A执行的过程中需要同步等待另外一个线程处理的结果&#xff0c;这种方式下&#xff0c;就是一种等待唤醒的机制。本篇我们来讲述等待唤醒机制的三种实现&#xff0c;以及对应的应用场景。 G…

Python基础进阶3:函数和方法不是一回事

你好&#xff0c;我是kelly&#xff0c;今天分享的是Python的函数与方法的不同点。 对于Python的函数和方法是不一样的&#xff0c;这一点需要注意下。 一、结论 1、不存在隐式传参&#xff0c;所有参数都是显式传递的是函数。 2、存在隐式传参的是方法&#xff0c;一般指隐式…

神经元科技发布AI agent—“萨蔓莎”

今天神经元科技发布AI agent—“萨蔓莎“&#xff08;Samantha &#xff09;&#xff01; 取名“萨蔓莎”&#xff0c;是来自于一部讲述AI的电影《HER》。 电影讲述的是电影讲述男子西奥多汤布里&#xff08;Theodore Twombly&#xff0c;饰&#xff09;与拟人化萨曼莎&#…

日志记录、跟踪和指标

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 日志记录、跟踪和指标是系统可观察性的三大支柱。 下图显示了它们的定义和典型架构。 记录 日志记录系统中的离散事件。例如&#xff0c;我们可以将传入请求或对…

挑战Python100题(8)

100+ Python challenging programming exercises 8 Question 71 Please write a program which accepts basic mathematic expression from console and print the evaluation result. 请编写一个从控制台接受基本数学表达式的程序,并打印评估结果。 Example: If the follo…

蜘蛛目标检测数据集VOC格式3900张

蜘蛛是一类广泛分布于地球各地的节肢动物&#xff0c;它们属于蛛形纲动物&#xff0c;是无脊椎动物的一个大类。蜘蛛的身体通常分为两个部分&#xff0c;头胸部和腹部&#xff0c;与其他节肢动物相比&#xff0c;蜘蛛的身体相对较小。 蜘蛛具有典型的八只腿&#xff0c;它们的…

结构体:枚举

#include<iostream> using namespace std; int main() {enum weekday { mon, tus, wed, thu, fri, sat,sun }; //声明枚举类型 enum weekday day; //定义枚举变量 int a, b, c, d, e, f, g, loop; //定义整型变量 char ch A; //定义字符变量 f thu; //按照题意&a…

搜索关键字高亮

文章目录 思路分析具体实现源码 不知道大家平常有没有自己空闲的时候写一些小demo的习惯呢&#xff1f;我个人觉得&#xff0c;在空闲的时候时不时写一个小功能&#xff0c;日积月累&#xff0c;当你以后遇到需要使用的时候&#xff0c;就可以直接拿来使用&#xff0c;当然了。…

Java基础02-Java编程基础

文章目录 变量&#xff08;Variables&#xff09;局部变量和成员变量局部变量&#xff08;Local Variables&#xff09;成员变量&#xff08;Instance Variables&#xff09; 标识符&#xff08;Identifiers&#xff09;八种基本数据类型原始数据类型&#xff08;Primitive Dat…

ESP32:整合存储配网信息和MQTT笔记

文章目录 1.给LED和KEY的所用IO增加配置项1.1 增加配置文件1.2 修改相应的c源码 2. 把mqtt\tcp的工程整合到一起2.1 在何处调用 mqtt_app_start() 3. 测试MQTT4. 完整的工程源码 有一段时间没有玩ESP32&#xff0c;很多知识点都忘记了。今天测试一下MQTT&#xff0c;做个笔记。…

『番外篇六』SwiftUI 取得任意视图全局位置的三种方法

概览 在 SwiftUI 开发中,利用描述性代码我们可以很轻松的构建各种丰富多彩的视图。我们可以设置它们的大小、位置、颜色并应用不计其数的修改器。 但是,小伙伴们是否想过在 SwiftUI 中如何获取一个视图的全局位置坐标呢? 在本篇博文中,您将学到如下内容: 概览1. SwiftU…

C语言中灵活多变的动态内存管理,malloc函数 free函数 calloc函数 realloc函数

文章目录 &#x1f680;前言&#x1f680;管理动态内存的函数✈️malloc函数✈️free函数✈️calloc函数✈️realloc函数 &#x1f680;在使用动态内存函数时的常见错误✈️对NULL指针的解引用✈️ 对动态开辟空间的越界访问✈️对非动态开辟内存使用free释放✈️使用free释放一…

数据统计的一些专业术语学习

数据统计的一些专业术语学习 1. 极差2. 方差3. 标准差4. 均值绝对差 1. 极差 数据统计的极差&#xff0c;又称全距&#xff0c;是指一组数据中最大值和最小值之差。 举个例子&#xff0c;如果我们有一组数据&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#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…

大数据Doris(四十五):物化视图选择最优

文章目录 物化视图选择最优 物化视图选择最优 下面详细解释一下第一步最优物化视图是被如何选择出来的。 这里分为两个步骤: 对候选集合进行一个过滤。只要是查询的结果能从物化视图数据计算(取部分行,部分列,或部分行列的聚合)出都可以留在候选集中,过滤完成后候选集合…

山西电力市场日前价格预测【2023-12-28】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-28&#xff09;山西电力市场全天平均日前电价为814.30元/MWh。其中&#xff0c;最高日前电价为1500.00元/MWh&#xff0c;预计出现在08:00~08:45,17:00~20:15。最低日前电价为394.61元/…

C#的checked关键字判断是否溢出

目录 一、定义 二、示例&#xff1a; 三、生成&#xff1a; 一、定义 使用checked关键字处理溢出。 在进行数学运算时&#xff0c;由于变量类型不同&#xff0c;数值的值域也有所不同。如果变量中的数值超出了变量的值域&#xff0c;则会出现溢出情况&#xff0c;出现溢出…

2023第三届中国高效大数据挑战赛A题思路及代码

一、题目介绍 赛题 A 中文文本纠错 中文文本纠错的任务主要是针对中文文本中出现的错误进行检测和纠正&#xff0c;属 于人工智能自然语言处理的研究子方向。中文文本纠错通常使用的场景有政务公 文、裁判文书、新闻出版等&#xff0c;中文文本纠错对于以中文作为母语的使用者…

nodejs+vue网上书城图书销售商城系统io69w

功能介绍 该系统将采用B/S结构模式&#xff0c;使用Vue和ElementUI框架搭建前端页面&#xff0c;后端使用Nodejs来搭建服务器&#xff0c;并使用MySQL&#xff0c;通过axios完成前后端的交互 系统的主要功能包括首页、个人中心、用户管理、图书类型管理、图书分类管理、图书信…

启动springboot时报错 APPLICATION FAILED TO START 包冲突

启动springboot时报错 APPLICATION FAILED TO START 包冲突 problem 具体日志如下 *************************** APPLICATION FAILED TO START ***************************Description:An attempt was made to call a method that does not exist. The attempt was made fr…