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…

莫烦Pytorch神经网络第二章代码修改

import torch import numpy as np""" Numpy Torch对比课程 """ # #tensor与numpy格式数据相互转换 # np_data np.arange(6).reshape((2,3)) # print(np_data) # # torch_data torch.from_numpy(np_data) # print(\n,torch_data) # # tensor2ar…

自定义字符类

当 VC不使用MFC&#xff0c;无法使用属于MFC的CString&#xff0c;为此自定义一个&#xff0c;先暂时使用&#xff0c;后续完善。 头文件&#xff1a; #pragma once#define MAX_LOADSTRING 100 // 最大字符数class CString {public:char *c_str, cSAr[MAX_LOADSTRING];WCHAR *w…

使用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 获得业务理解并定义项目目…

Android 事件处理

事件就是用户对图形的操作&#xff0c;在android手机和平板电脑上&#xff0c;主要包含物理按键事件和触摸屏事件两类。物理按键事件包含&#xff1a;按下、抬起、长按等&#xff1b;触摸屏事件主要包含按下、抬起、滚动、双击等。 在View中提供了onTouchEvent()方法&#xff0…

莫烦Pytorch神经网络第三章代码修改

3.1Regression回归 import torch import torch.nn.functional as F from torch.autograd import Variable import matplotlib.pyplot as plt""" 创建数据 """x torch.unsqueeze(torch.linspace(-1,1,100),dim1) y x.pow(2) 0.2*torch.rand(x…

为什么饼图有问题

介绍 (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…

New Distinct Substrings(后缀数组)

New Distinct Substrings&#xff08;后缀数组&#xff09; 给定一个字符串&#xff0c;求不相同的子串的个数。\(n<50005\)。 显然&#xff0c;任何一个子串一定是后缀上的前缀。先&#xff08;按套路&#xff09;把后缀排好序&#xff0c;对于当前的后缀\(S_i\)&#xff0…

Android dependency 'com.android.support:support-v4' has different version for the compile (26.1.0...

在项目中加入react-native-camera的时候 出现的错误. 解决方案: 修改 implementation project(:react-native-camera)为 implementation (project(:react-native-camera)) {exclude group: "com.android.support"}查看原文 Could not find play-services-basement.aa…

先知模型 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 …

莫烦Pytorch神经网络第四章代码修改

4.1CNN卷积神经网络 import torch import torch.nn as nn from torch.autograd import Variable import torch.utils.data as Data import torchvision import matplotlib.pyplot as pltEPOCH 1 BATCH_SIZE 50 LR 0.001 DOWNLOAD_MNIST False #如果数据集已经下载到…

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_…

loj #6278. 数列分块入门 2

题目 题解 区间修改&#xff0c;询问区间小于c的个数。分块排序&#xff0c;用vector。至于那个块的大小&#xff0c;好像要用到均值不等式 我不太会。。。就开始一个个试&#xff0c;发现sizsqrt(n)/4时最快&#xff01;&#xff01;&#xff01;明天去学一下算分块复杂度的方…

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

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

莫烦Pytorch神经网络第五章代码修改

5.1动态Dynamic import torch from torch import nn import numpy as np import matplotlib.pyplot as plt# torch.manual_seed(1) # reproducible# Hyper Parameters INPUT_SIZE 1 # rnn input size / image width LR 0.02 # learning rateclass…

鲜为人知的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…

网页JS获取当前地理位置(省市区)

网页JS获取当前地理位置&#xff08;省市区&#xff09; 一、总结 一句话总结&#xff1a;ip查询接口 二、网页JS获取当前地理位置&#xff08;省市区&#xff09; 眼看2014又要过去了&#xff0c;翻翻今年的文章好像没有写几篇&#xff0c;忙真的或许已经不能成为借口了&#…