内容简介:提到JDK动态代理,相信很多人并不陌生。然而,对于动态代理的实现原理,以及如何编码实现动态代理功能,可能知道的人就比较少了。接下一来,我们就一起来看看JDK动态代理的基本原理,以及如何通过Javassist进行模拟实现。以下是一个基于JDK动态代理的hello world示例,在很多地方都可以看到类似的版本。通过设置参数
提到JDK动态代理,相信很多人并不陌生。然而,对于动态代理的实现原理,以及如何编码实现动态代理功能,可能知道的人就比较少了。接下一来,我们就一起来看看JDK动态代理的基本原理,以及如何通过Javassist进行模拟实现。
JDK动态代理
示例
以下是一个基于JDK动态代理的hello world示例,在很多地方都可以看到类似的版本。
public class DynamicProxyTest {
interface IHello {
void sayHello();
}
static class Hello implements IHello {
@Override
public 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);
}
@Override
public 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 遍历接口方法
<code>CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for (int i = 0; i < ctMethods.length; i++) {
// 核心逻辑在下方
}
</code><button>复制</button>
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 NewProxyClass
CtClass 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 IHello
proxyCc.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方法的返回类型为Object
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(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 {
@Override
public 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;
}
@Override
public 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官网: http://www.javassist.org/
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTTP Essentials
Stephen A. Thomas、Stephen Thomas / Wiley / 2001-03-08 / USD 34.99
The first complete reference guide to the essential Web protocol As applications and services converge and Web technologies not only assume HTTP but require developers to manipulate it, it is be......一起来看看 《HTTP Essentials》 这本书的介绍吧!