Javassist实现JDK动态代理

提到JDK动态代理,相信很多人并不陌生。然而,对于动态代理的实现原理,以及如何编码实现动态代理功能,可能知道的人就比较少了。接下一来,我们就一起来看看JDK动态代理的基本原理,以及如何通过Javassist进行模拟实现。

JDK动态代理

示例

以下是一个基于JDK动态代理的hello world示例,在很多地方都可以看到类似的版本。

public class DynamicProxyTest {interface IHello {void sayHello();}static class Hello implements IHello {@Overridepublic void sayHello() {System.out.println("hello world");}}static class DynamicProxy implements InvocationHandler {Object originalObj;Object bind(Object originalObj) {this.originalObj = originalObj;return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("pre method");Object result = method.invoke(originalObj, args);System.out.println("post method");return result;}}public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");IHello hello = (IHello) new DynamicProxy().bind(new Hello());hello.sayHello();}
}
复制代码

生成代理类源码

通过设置参数 sun.misc.ProxyGenerator.saveGeneratedFiles为true,在执行main函数之后,我们将得到一份$Proxy0.class文件,它就是Hello的代理类。

经过反编译,得到$Proxy0的源码(省略了无关内容)如下:

final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler paramInvocationHandler) {super(paramInvocationHandler);}public final void sayHello() {try {this.h.invoke(this, m3, null);return;} catch (Error | RuntimeException localError) {throw localError;} catch (Throwable localThrowable) {throw new UndeclaredThrowableException(localThrowable);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });m3 = Class.forName("DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException localNoSuchMethodException) {throw new NoSuchMethodError(localNoSuchMethodException.getMessage());} catch (ClassNotFoundException localClassNotFoundException) {throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}
复制代码

主要实现原理

  • 动态生成一个代理类,实现IHello接口;
  • 代理类继承Proxy类,提供一个实例变量InvocationHandler h用于保存代理逻辑(此外,Proxy还提供了相关代理类处理逻辑);
  • 代理类声明一系列Method类变量,用于保存接口相关的反射方法;
  • 代理类实现相关接口方法,核心逻辑是调用InvocationHandler的invoke方法,并传入3个参数:当前代理类对象、接口反射方法对象、实际方法参数。

Javassist实现JDK动态代理

前面简单分析了JDK动态代理的基本原理,其中,最核心的逻辑在于如何生成动态代理类,也就是 java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法的实现。

接下来我们将通过Javassist一步步实现newProxyInstance方法。

1. 定义接口

接口基本与Proxy.newProxyInstance相同。为简单说明,我们这里只定义了一个接口类型参数Class<?>而不是数组。

public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) {...
}
复制代码

2. 创建动态代理类

Javassist可以通过简单的Java API来操作源代码,这样就可以在不了解Java字节码相关知识的情况下,动态生成类或修改类的行为。

创建名称为NewProxyClass的代理类。

ClassPool pool = ClassPool.getDefault();
CtClass proxyCc = pool.makeClass("NewProxyClass");
复制代码

3. 添加实例变量InvocationHandler

添加类型为InvocationHandler的实例变量h。

CtClass handlerCc = pool.get(InvocationHandler.class.getName());
/* 生成代码:private InvocationHandler h; */
CtField handlerField = new CtField(handlerCc, "h", proxyCc);
handlerField.setModifiers(AccessFlag.PRIVATE);
proxyCc.addField(handlerField);
复制代码

4. 添加构造函数

创建构造函数,参数类型为InvocationHandler。

// 生成构造函数:public NewProxyClass(InvocationHandler h) { this.h = h; }
CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
ctConstructor.setBody("$0.h = $1;");
proxyCc.addConstructor(ctConstructor);
复制代码

其中,$0代表this, $1代表构造函数的第1个参数。

5. 实现IHello接口声明

// 生成接口实现声明:public class NewProxyClass implements IHello
CtClass interfaceCc = pool.get(interfaceClass.getName());
proxyCc.addInterface(interfaceCc);
复制代码

6. 实现IHello相关接口方法

6.1 遍历接口方法

CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for (int i = 0; i < ctMethods.length; i++) {// 核心逻辑在下方
}
复制代码

6.2 代理方法实现

由于代理类调用invoke方法需要传入接口的反射方法对象(Method),因此,我们需要为每个方法添加一个可复用的Method类变量。

6.2.1 反射方法对象声明及初始化

/* 构造方法参数,如:new Class[] { String.class, Boolean.TYPE, Object.class } */
String classParamsStr = "new Class[0]";
if (ctMethods[i].getParameterTypes().length > 0) {for (CtClass clazz : ctMethods[i].getParameterTypes()) {classParamsStr = ((classParamsStr == "new Class[0]") ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";}classParamsStr = "new Class[] {" + classParamsStr + "}";
}
// 字段生成模板
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
// 根据模板生成方法及参数构造方法字段生成语句
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
// 为代理类添加反射方法字段
CtField methodField = CtField.make(methodFieldBody, proxyCc);
proxyCc.addField(methodField);
复制代码

通过以上逻辑,将生成类似代码如下:

private static Method m0 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello", new Class[0]);
private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });
private static Method m2 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3", new Class[] { String.class, Boolean.TYPE, Object.class });
复制代码

6.2.2 接口方法体实现

// invoke调用逻辑. 其中$args是实际方法传入的参数数组
String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";// 如果方法有返回类型,则需要转换为相应类型后返回
if (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {// 对8个基本类型进行转型// 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";}// 对于非基本类型直接转型即可else {methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;}
}
methodBody += ";";/* 为代理类添加方法 */
CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),ctMethods[i].getParameterTypes(), proxyCc);
newMethod.setBody(methodBody);
proxyCc.addMethod(newMethod);
复制代码

通过以上逻辑,将生成类似代码如下:

public void sayHello() {this.h.invoke(this, m0, new Object[0]);
}public String sayHello2(int paramInt) {return (String)this.h.invoke(this, m1, new Object[] { new Integer(paramInt) });
}public int sayHello3(String paramString, boolean paramBoolean, Object paramObject) {return ((Integer)this.h.invoke(this, m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
}
复制代码

7. 生成代理类字节码

以下语句,将生成代理类字节码:D:/tmp/NewProxyClass.class

proxyCc.writeFile("D:/tmp"); // 该步骤可选
复制代码

8. 生成代理对象

最后,通过调用第3步创建的构造函数,传入InvocationHandler对象,生成并返回代理类。

Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);
return proxy;
复制代码

完整代码

public class ProxyFactory {public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) throws Throwable {ClassPool pool = ClassPool.getDefault();// 1.创建代理类:public class NewProxyClassCtClass proxyCc = pool.makeClass("NewProxyClass");/* 2.给代理类添加字段:private InvocationHandler h; */CtClass handlerCc = pool.get(InvocationHandler.class.getName());CtField handlerField = new CtField(handlerCc, "h", proxyCc); // CtField(CtClass fieldType, String fieldName, CtClass addToThisClass)handlerField.setModifiers(AccessFlag.PRIVATE);proxyCc.addField(handlerField);/* 3.添加构造函数:public NewProxyClass(InvocationHandler h) { this.h = h; } */CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);ctConstructor.setBody("$0.h = $1;"); // $0代表this, $1代表构造函数的第1个参数proxyCc.addConstructor(ctConstructor);/* 4.为代理类添加相应接口方法及实现 */CtClass interfaceCc = pool.get(interfaceClass.getName());// 4.1 为代理类添加接口:public class NewProxyClass implements IHelloproxyCc.addInterface(interfaceCc);// 4.2 为代理类添加相应方法及实现CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();for (int i = 0; i < ctMethods.length; i++) {String methodFieldName = "m" + i; // 新的方法名// 4.2.1 为代理类添加反射方法字段// 如:private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });/* 构造反射字段声明及赋值语句 */String classParamsStr = "new Class[0]"; // 方法的多个参数类型以英文逗号分隔if (ctMethods[i].getParameterTypes().length > 0) { // getParameterTypes获取方法参数类型列表for (CtClass clazz : ctMethods[i].getParameterTypes()) {classParamsStr = (("new Class[0]".equals(classParamsStr)) ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";}classParamsStr = "new Class[] {" + classParamsStr + "}";}String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);// 为代理类添加反射方法字段. CtField.make(String sourceCodeText, CtClass addToThisClass)CtField methodField = CtField.make(methodFieldBody, proxyCc);proxyCc.addField(methodField);System.out.println("methodFieldBody: " + methodFieldBody);/* 4.2.2 为方法添加方法体 *//* 构造方法体. this.h.invoke(this, 反射字段名, 方法参数列表); */String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";// 如果方法有返回类型,则需要转换为相应类型后返回,因为invoke方法的返回类型为Objectif (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {// 对8个基本类型进行转型// 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";} else { // 对于非基本类型直接转型即可methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;}}methodBody += ";";/* 为代理类添加方法. CtMethod(CtClass returnType, String methodName, CtClass[] parameterTypes, CtClass addToThisClass) */CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),ctMethods[i].getParameterTypes(), proxyCc);newMethod.setBody(methodBody);proxyCc.addMethod(newMethod);System.out.println("Invoke method: " + methodBody);}proxyCc.writeFile("D:/tmp");// 5.生成代理实例. 将入参InvocationHandler h设置到代理类的InvocationHandler h变量@SuppressWarnings("unchecked")Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);return proxy;}}public interface IHello {int sayHello3(String a, boolean b, Object c);
}public class Hello implements IHello {@Overridepublic int sayHello3(String a, boolean b, Object c) {String abc = a + b + c;System.out.println("a + b + c=" + abc);return abc.hashCode();}
}public class CustomHandler implements InvocationHandler {private Object obj;public CustomHandler(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("pre method");Object result = method.invoke(obj, args);System.out.println("post method");return result;}}public class ProxyTest {public static void main(String[] args) throws Throwable {IHello hello = new Hello();CustomHandler customHandler = new CustomHandler(hello);IHello helloProxy = (IHello) ProxyFactory.newProxyInstance(hello.getClass().getClassLoader(), IHello.class, customHandler);System.out.println();System.out.println("a+false+Object=" + helloProxy.sayHello3("a", false, new Object()));}}
复制代码

执行结果

methodFieldBody: private static java.lang.reflect.Method m0=Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3", new Class[] {java.lang.String.class,boolean.class,java.lang.Object.class});
Invoke method: return ((java.lang.Integer) $0.h.invoke(0, m0,args)).intValue();

pre method
a + b + c=afalsejava.lang.Object@504bae78
post method
a+false+Object=-903110407


参考

Javassist官网:www.javassist.org/

个人公众号

更多文章,请关注公众号:二进制之路

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

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

相关文章

数据图表可视化_数据可视化如何选择正确的图表第1部分

数据图表可视化According to the World Economic Forum, the world produces 2.5 quintillion bytes of data every day. With so much data, it’s become increasingly difficult to manage and make sense of it all. It would be impossible for any person to wade throug…

Keras框架:实例分割Mask R-CNN算法实现及实现

实例分割 实例分割&#xff08;instance segmentation&#xff09;的难点在于&#xff1a; 需要同时检测出目标的位置并且对目标进行分割&#xff0c;所以这就需要融合目标检测&#xff08;框出目标的位置&#xff09;以及语义分割&#xff08;对像素进行分类&#xff0c;分割…

机器学习 缺陷检测_球检测-体育中的机器学习。

机器学习 缺陷检测&#x1f6a9; 目标 (&#x1f6a9;Objective) We want to evaluate the quickest way to detect the ball in a sport event in order to develop an Sports AI without spending a million dollars on tech or developers. Quickly we find out that detec…

使用python和javascript进行数据可视化

Any data science or data analytics project can be generally described with the following steps:通常可以通过以下步骤来描述任何数据科学或数据分析项目&#xff1a; Acquiring a business understanding & defining the goal of a project 获得业务理解并定义项目目…

为什么饼图有问题

介绍 (Introduction) It seems as if people are split on pie charts: either you passionately hate them, or you are indifferent. In this article, I am going to explain why pie charts are problematic and, if you fall into the latter category, what you can do w…

先知模型 facebook_使用Facebook先知进行犯罪率预测

先知模型 facebookTime series prediction is one of the must-know techniques for any data scientist. Questions like predicting the weather, product sales, customer visit in the shopping center, or amount of inventory to maintain, etc - all about time series …

github gists 101使代码共享漂亮

If you’ve been going through Medium, looking at technical articles, you’ve undoubtedly seen little windows that look like the below:如果您一直在阅读Medium&#xff0c;并查看技术文章&#xff0c;那么您无疑会看到类似于以下内容的小窗口&#xff1a; def hello_…

基于Netty的百万级推送服务设计要点

1. 背景1.1. 话题来源最近很多从事移动互联网和物联网开发的同学给我发邮件或者微博私信我&#xff0c;咨询推送服务相关的问题。问题五花八门&#xff0c;在帮助大家答疑解惑的过程中&#xff0c;我也对问题进行了总结&#xff0c;大概可以归纳为如下几类&#xff1a;1&#x…

鲜为人知的6个黑科技网站_6种鲜为人知的熊猫绘图工具

鲜为人知的6个黑科技网站Pandas is the go-to Python library for data analysis and manipulation. It provides numerous functions and methods that expedice the data analysis process.Pandas是用于数据分析和处理的Python库。 它提供了加速数据分析过程的众多功能和方法…

VRRP网关冗余

实验要求 1、R1创建环回口&#xff0c;模拟外网 2、R2&#xff0c;R3使用VRRP技术 3、路由器之间使用EIGRP路由协议  实验拓扑  实验配置  R1(config)#interface loopback 0R1(config-if)#ip address 1.1.1.1 255.255.255.0R1(config-if)#int e0/0R1(config-if)#ip addr…

大熊猫卸妆后_您不应错过的6大熊猫行动

大熊猫卸妆后数据科学 (Data Science) Pandas is used mainly for reading, cleaning, and extracting insights from data. We will see an advanced use of Pandas which are very important to a Data Scientist. These operations are used to analyze data and manipulate…

数据eda_关于分类和有序数据的EDA

数据eda数据科学和机器学习统计 (STATISTICS FOR DATA SCIENCE AND MACHINE LEARNING) Categorical variables are the ones where the possible values are provided as a set of options, it can be pre-defined or open. An example can be the gender of a person. In the …

jdk重启后步行_向后介绍步行以一种新颖的方式来预测未来

jdk重启后步行“永远不要做出预测&#xff0c;尤其是关于未来的预测。” (KK Steincke) (“Never Make Predictions, Especially About the Future.” (K. K. Steincke)) Does this picture portray a horse or a car? 这张照片描绘的是马还是汽车&#xff1f; How likely is …

mongodb仲裁者_真理的仲裁者

mongodb仲裁者Coming out of college with a background in mathematics, I fell upward into the rapidly growing field of data analytics. It wasn’t until years later that I realized the incredible power that comes with the position. As Uncle Ben told Peter Par…

优化 回归_使用回归优化产品价格

优化 回归应用数据科学 (Applied data science) Price and quantity are two fundamental measures that determine the bottom line of every business, and setting the right price is one of the most important decisions a company can make. Under-pricing hurts the co…

大数据数据科学家常用面试题_进行数据科学工作面试

大数据数据科学家常用面试题During my time as a Data Scientist, I had the chance to interview my fair share of candidates for data-related roles. While doing this, I started noticing a pattern: some kinds of (simple) mistakes were overwhelmingly frequent amo…

scrapy模拟模拟点击_模拟大流行

scrapy模拟模拟点击复杂系统 (Complex Systems) In our daily life, we encounter many complex systems where individuals are interacting with each other such as the stock market or rush hour traffic. Finding appropriate models for these complex systems may give…

vue.js python_使用Python和Vue.js自动化报告过程

vue.js pythonIf your organization does not have a data visualization solution like Tableau or PowerBI nor means to host a server to deploy open source solutions like Dash then you are probably stuck doing reports with Excel or exporting your notebooks.如果…

plsql中导入csvs_在命令行中使用sql分析csvs

plsql中导入csvsIf you are familiar with coding in SQL, there is a strong chance you do it in PgAdmin, MySQL, BigQuery, SQL Server, etc. But there are times you just want to use your SQL skills for quick analysis on a small/medium sized dataset.如果您熟悉SQ…

计算机科学必读书籍_5篇关于数据科学家的产品分类必读文章

计算机科学必读书籍Product categorization/product classification is the organization of products into their respective departments or categories. As well, a large part of the process is the design of the product taxonomy as a whole.产品分类/产品分类是将产品…