经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
【深度思考】聊聊JDK动态代理原理
来源:cnblogs  作者:申城异乡人  时间:2023/4/17 9:37:50  对本文有异议

1. 示例

首先,定义一个接口:

  1. public interface Staff {
  2. void work();
  3. }

然后,新增一个类并实现上面的接口:

  1. public class Coder implements Staff {
  2. @Override
  3. public void work() {
  4. System.out.println("认真写bug……");
  5. }
  6. }

假设现在有这么一个需求:在不改动以上类代码的前提下,对该方法增加一些前置操作或者后置操作。

接下来就来讲解下,如何使用JDK动态代理来实现这个需求。

首先,自定义一个调用处理器,实现java.lang.reflect.InvocationHandler接口并重写invoke方法:

  1. public class AttendanceInvocationHandler implements InvocationHandler {
  2. private final Object target;
  3. public AttendanceInvocationHandler(Object target) {
  4. this.target = target;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("上班打卡……");
  9. Object invoke = method.invoke(target, args);
  10. System.out.println("下班打卡……");
  11. return invoke;
  12. }
  13. }

重点看下Object invoke = method.invoke(target, args);,该行代码会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。

然后,新建个测试类,看下JDK动态代理如何使用:

  1. public class JdkProxyTest {
  2. public static void main(String[] args) {
  3. Coder coder = new Coder();
  4. AttendanceInvocationHandler h = new AttendanceInvocationHandler(coder);
  5. // 创建代理对象
  6. Object proxyInstance = Proxy.newProxyInstance(coder.getClass().getClassLoader(),
  7. coder.getClass().getInterfaces(),
  8. h);
  9. Staff staff = (Staff) proxyInstance;
  10. staff.work();
  11. }
  12. }

运行以上代码,效果如下图所示:

从运行结果可以看出,在目标方法的前后,执行了自定义的操作。

2. 原理

这里理解2个概念,目标对象和代理对象,

目标对象是真正要调用的对象,上面示例中的Coder类就是目标对象,

代理对象是JDK自动生成的对象,在代理对象内部会去调用目标对象的目标方法。

JDK动态代理的核心就是上面示例中的Proxy.newProxyInstance方法,方法签名如下图所示:

第1个参数传入的是目标对象的ClassLoader,第2个参数传入的是目标对象的接口信息,第3个参数传入的是自定义的InvocationHandler。

然后看下该方法的实现逻辑,先看第1处重点:

注释翻译过来是:查找或者生成指定的代理类。

该方法会生成代理类的字节码文件(也可能是从缓存中读取),核心逻辑在ProxyClassFactory类的apply方法中,

该方法中定义了生成的代理类的包名以及文件名:

因此默认情况下,自动生成的代理类名称是com.sun.proxy.$Proxy0

该方法最后会生成代理类的字节码,默认情况下不会保存到文件系统,但可以通过参数指定保存到文件系统:

可以看出,保存不保存到文件系统,受saveGeneratedFiles的影响,其定义如下所示:

  1. private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

所以可以通过指定sun.misc.ProxyGenerator.saveGeneratedFiles参数来让生成的代理类字节码文件保存到文件系统中。

然后看第2处重点:

先是获取构造函数,然后是生成代理类对象的实例。

3. 为什么必须要基于接口?

思考一个问题,为什么JDK动态代理必须要基于接口,带着这个问题,我们看下动态生成的代理类com.sun.proxy.$Proxy0长什么样子?

JVM参数里添加参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,然后启动上面示例中的测试代码:

生成的代理类字节码文件保存在项目根目录下的com/sun/proxy目录下:

在IDEA中打开后,如下图所示:

在静态代码块中,对静态变量m0、m1、m2、m3进行了赋值,其中m3是要执行的目标方法。

在构造方法中,执行的是super(var1);,也就是父类Proxy的构造方法:

该方法是将我们自定义的InvocationHandler赋值给了父类的变量h。

而以下测试代码实际执行的是代理类$Proxy0里的work方法:

  1. Staff staff = (Staff) proxyInstance;
  2. staff.work();

代理类$Proxy0里的work方法实际执行的是自定义InvocationHandler里的invoke方法:

因此在执行目标方法前后,执行了自定义的前置操作和后置操作。

了解了这个调用过程,就理解了为什么JDK动态代理必须要基于接口,因为动态生成的代理类已经继承了类java.lang.reflect.Proxy

而Java又是单继承的,如果想要继续对类进行扩展,只能通过实现接口的方式。

文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

原文链接:https://www.cnblogs.com/zwwhnly/p/17324797.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号