提到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(args)).intValue();

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


参考

Javassist官网:www.javassist.org/

个人公众号

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

Javassist实现JDK动态代理相关推荐

  1. 深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

    文章目录 系列文章索引 一.动手实现一个动态代理框架 1.初识javassist 2.使用javassist实现一个动态代理框架 二.JDK动态代理 1.编码实现 2.基本原理 (1)getProxy ...

  2. 【动态代理】从源码实现角度剖析JDK动态代理

    相比于静态代理,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象.动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代 ...

  3. 浅谈Spring中JDK动态代理与CGLIB动态代理

    前言 Spring是Java程序员基本不可能绕开的一个框架,它的核心思想是IOC(控制反转)和AOP(面向切面编程).在Spring中这两个核心思想都是基于设计模式实现的,IOC思想的实现基于工厂模式 ...

  4. 定时器的实现原理 不消耗cpu_一直在使用JDK动态代理, 不明白原理如何实现?

    01.前言 本来动态代理知识点并不在最近文章列表中, 但是在 mybatis 注册 mapper 接口使用到了, 知其然知其所以然 本篇文章是围绕 JDK 动态代理来进行说明, 需要读者掌握基本的反射 ...

  5. (转)面试必备技能:JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

  6. 【spring】初识aop(面向切面编程) 使用jdk动态代理

    BankServiceIImple.java 代码实现: package com.zzxtit.aop;import java.math.BigDecimal;public interface Ban ...

  7. 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理...

    [原创]分布式之缓存击穿 什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询, ...

  8. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

  9. aop的四种增强以及JDK动态代理、Cglib动态代理

    动态代理 AOP底层实现:有接口自动应用的就是JDK动态代理 (1).JDK 在运行时运行时注入 本质:在内存中构建出接口的实现类 特点:被代理对象,必须有接口 实例: import java.lan ...

最新文章

  1. git 基于发布分支的开发
  2. 变量的定义 声明和初始化和static
  3. python培训中心-想学python,上海Python培训中心哪个好?
  4. 【转】 [C/OC的那点事儿]NSMutableArray排序的三种实现(依赖学生成绩管理系统).
  5. HTML5概要与新增标签
  6. 工作252:uniapp--实战--uview---tabber
  7. CodeForces 103D Time to Raid Cowavans 分块+dp
  8. MP4转AVI转AMV教程:教你把B站视频导入你的MP3MP4随身听播放器
  9. [摩斯密码表]摩斯密码对照表
  10. 027 多分支选择结构
  11. 一站式WPF--依赖属性(DependencyProperty)一
  12. Ubuntu10.10下安装Tor,PolipoVidalia
  13. html - 空格符号 - 字符实体 - 预留字
  14. calico源码分析-ipam(1)
  15. web前端html怎么求最大值和最小值,求js数组的最大值和最小值的四种方法
  16. 【GPS - NMEA-0183协议】
  17. Android Studio清单文件合并规则
  18. C语言中宏定义宏编译的使用#if,#ifdef,#ifndef,#elif,#else,#endif,defined使用详解,可以实现和if else一样的效果
  19. ​前端VueRouter解析
  20. ftp下载怎么操作,使用教程

热门文章

  1. css设置a连接禁用样式_使用CSS禁用链接
  2. scala语言示例_var关键字与Scala中的示例
  3. Java——集合的遍历之迭代遍历
  4. matlab lu分解求线性方程组_计算方法(二)直接三角分解法解线性方程组
  5. oracle正则匹配全部,sql – 返回Oracle中正则表达式的所有匹配项
  6. 第七届山东省Acm程序设计竞赛赛后总结
  7. 事务隔离级别动图演示
  8. 网络基础2-3(TCP协议,三次握手,四次挥手,TIME_WAIT状态的作用,TCP如何保证可靠传输,TCP连接中状态转化,滑动窗口,流量控制,快速重传,拥塞窗口,延迟应答,捎带应答,粘包问题)
  9. Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)
  10. 用c语言构建二叉树(重点)