Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理

时间:2022-05-07 21:20:36

Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理

JDK动态代理的实现及原理

作者:二青

邮箱:xtfggef@gmail.com     微博:http://weibo.com/xtfggef

动态代理,听上去很高大上的技术,在Java里应用广泛,尤其是在hibernatespring这两种框架里,在AOP,权限控制,事务管理等方面都有动态代理的实现。JDK本身有实现动态代理技术,但是略有限制,即被代理的类必须实现某个接口,否则无法使用JDK自带的动态代理,因此,如果不满足条件,就只能使用另一种更加灵活,功能更加强大的动态代理技术—— CGLIB。Spring里会自动在JDK的代理和CGLIB之间切换,同时我们也可以强制Spring使用CGLIB。下面我们就动态代理方面的知识点从头至尾依次介绍一下。

我们先来看一个例子:

新建一个接口,UserService.java, 只有一个方法add()。

  1. package com.adam.java.basic;
  2. public interface UserService {
  3. public abstract void add();
  4. }

建一个该接口的实现类UserServiceImpl.java

  1. package com.adam.java.basic;
  2. public class UserServiceImpl implements UserService {
  3. @Override
  4. public void add() {
  5. System.out.println("----- add -----");
  6. }
  7. }

建一个代理处理类MyInvocationHandler.java

  1. package com.adam.java.basic;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. public class MyInvocationHandler implements InvocationHandler {
  6. private Object target;
  7. public MyInvocationHandler(Object target) {
  8. super();
  9. this.target = target;
  10. }
  11. public Object getProxy() {
  12. return Proxy.newProxyInstance(Thread.currentThread()
  13. .getContextClassLoader(), target.getClass().getInterfaces(),
  14. this);
  15. }
  16. @Override
  17. public Object invoke(Object proxy, Method method, Object[] args)
  18. throws Throwable {
  19. System.out.println("----- before -----");
  20. Object result = method.invoke(target, args);
  21. System.out.println("----- after -----");
  22. return result;
  23. }
  24. }

测试类

  1. package com.adam.java.basic;
  2. public class DynamicProxyTest {
  3. public static void main(String[] args) {
  4. UserService userService = new UserServiceImpl();
  5. MyInvocationHandler invocationHandler = new MyInvocationHandler(
  6. userService);
  7. UserService proxy = (UserService) invocationHandler.getProxy();
  8. proxy.add();
  9. }
  10. }

执行测试类,得到如下输出:
----- before -----
----- add -----
----- after -----
到这里,我们应该会想到点问题:
1. 这个代理对象是由谁且怎么生成的?
2. invoke方法是怎么调用的?
3. invoke和add方法有什么对应关系?
4. 生成的代理对象是什么样子的?
带着这些问题,我们看一下源码。首先,我们的入口便是上面测试类里的getProxy()方法,我们跟进去,看看这个方法:

  1. public Object getProxy() {
  2. return Proxy.newProxyInstance(Thread.currentThread()
  3. .getContextClassLoader(), target.getClass().getInterfaces(),this);
  4. }

也就是说,JDK的动态代理,是通过一个叫Proxy的类来实现的,我们继续跟进去,看看Proxy类的newProxyInstance()方法。先来看看JDK的注释:

  1. /**
  2. * Returns an instance of a proxy class for the specified interfaces
  3. * that dispatches method invocations to the specified invocation
  4. * handler.
  5. *
  6. * <p>{@code Proxy.newProxyInstance} throws
  7. * {@code IllegalArgumentException} for the same reasons that
  8. * {@code Proxy.getProxyClass} does.
  9. *
  10. * @param   loader the class loader to define the proxy class
  11. * @param   interfaces the list of interfaces for the proxy class
  12. *          to implement
  13. * @param   h the invocation handler to dispatch method invocations to
  14. * @return  a proxy instance with the specified invocation handler of a
  15. *          proxy class that is defined by the specified class loader
  16. *          and that implements the specified interfaces

根据JDK注释我们得知,newProxyInstance方法最终将返回一个实现了指定接口的类的实例,其三个参数分别是:ClassLoader,指定的接口及我们自己定义的InvocationHandler类。我摘几条关键的代码出来,看看这个代理类的实例对象到底是怎么生成的。

  1. Class<?> cl = getProxyClass0(loader, intfs);
  2. ...
  3. final Constructor<?> cons = cl.getConstructor(constructorParams);
  4. ...
  5. return cons.newInstance(new Object[]{h});

有兴趣的同学可以自己看看JDK的源码,当前我用的版本是JDK1.8.25,每个版本实现方式可能会不一样,但基本一致,请研究源码的同学注意这一点。上面的代码表明,首先通过getProxyClass获得这个代理类,然后通过c1.getConstructor()拿到构造函数,最后一步,通过cons.newInstance返回这个新的代理类的一个实例,注意:调用newInstance的时候,传入的参数为h,即我们自己定义好的InvocationHandler类,先记着这一步,后面我们就知道这里这样做的原因。

其实这三条代码,核心就是这个getProxyClass方法,另外两行代码是Java反射的应用,和我们当前的兴趣点没什么关系,所以我们继续研究这个getProxyClass方法。这个方法,注释很简单,如下:

  1. /*
  2. * Look up or generate the designated proxy class.
  3. */
  4. Class<?> cl = getProxyClass0(loader, intfs);

就是生成这个关键的代理类,我们跟进去看一下。

  1. private static Class<?> getProxyClass0(ClassLoader loader,
  2. Class<?>... interfaces) {
  3. if (interfaces.length > 65535) {
  4. throw new IllegalArgumentException("interface limit exceeded");
  5. }
  6. // If the proxy class defined by the given loader implementing
  7. // the given interfaces exists, this will simply return the cached copy;
  8. // otherwise, it will create the proxy class via the ProxyClassFactory
  9. return proxyClassCache.get(loader, interfaces);
  10. }

这里用到了缓存,先从缓存里查一下,如果存在,直接返回,不存在就新创建。在这个get方法里,我们看到了如下代码:
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
此处提到了apply(),是Proxy类的内部类ProxyClassFactory实现其接口的一个方法,具体实现如下:

  1. public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  2. Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
  3. for (Class<?> intf : interfaces) {
  4. /*
  5. * Verify that the class loader resolves the name of this
  6. * interface to the same Class object.
  7. */
  8. Class<?> interfaceClass = null;
  9. try {
  10. interfaceClass = Class.forName(intf.getName(), false, loader);
  11. } catch (ClassNotFoundException e) {
  12. }
  13. if (interfaceClass != intf) {
  14. throw new IllegalArgumentException(
  15. intf + " is not visible from class loader");
  16. }...

看到Class.forName()的时候,我想大多数人会笑了,终于看到熟悉的方法了,没错!这个地方就是要加载指定的接口,既然是生成类,那就要有对应的class字节码,我们继续往下看:

  1. /*
  2. * Generate the specified proxy class.
  3. */
  4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  5. proxyName, interfaces, accessFlags);
  6. try {
  7. return defineClass0(loader, proxyName,
  8. proxyClassFile, 0, proxyClassFile.length);

这段代码就是利用ProxyGenerator为我们生成了最终代理类的字节码文件,即getProxyClass0()方法的最终返回值。所以让我们回顾一下最初的四个问题:

1. 这个代理对象是由谁且怎么生成的?

2. invoke方法是怎么调用的?

3. invoke和add方法有什么对应关系?

4. 生成的代理对象是什么样子的?

对于第一个问题,我想答案已经清楚了,我再屡一下思路:由Proxy类的getProxyClass0()方法生成目标代理类,然后拿到该类的构造方法,最后通过反射的newInstance方法,产生代理类的实例对象。

接下来,我们看看其他的三个方法,我想先从第四个入手,因为有了上面的生成字节码的代码,那我们可以模仿这一步,自己生成字节码文件看看,所以,我用如下代码,生成了这个最终的代理类。

  1. package com.adam.java.basic;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import sun.misc.ProxyGenerator;
  5. public class DynamicProxyTest {
  6. public static void main(String[] args) {
  7. UserService userService = new UserServiceImpl();
  8. MyInvocationHandler invocationHandler = new MyInvocationHandler(
  9. userService);
  10. UserService proxy = (UserService) invocationHandler.getProxy();
  11. proxy.add();
  12. String path = "C:/$Proxy0.class";
  13. byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
  14. UserServiceImpl.class.getInterfaces());
  15. FileOutputStream out = null;
  16. try {
  17. out = new FileOutputStream(path);
  18. out.write(classFile);
  19. out.flush();
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. } finally {
  23. try {
  24. out.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. }

上面测试方法里的proxy.add(),此处的add()方法,就已经不是原始的UserService里的add()方法了,而是新生成的代理类的add()方法,我们将生成的$Proxy0.class文件用jd-gui打开,我去掉了一些代码,add()方法如下:

  1. public final void add()
  2. throws
  3. {
  4. try
  5. {
  6. this.h.invoke(this, m3, null);
  7. return;
  8. }
  9. catch (Error|RuntimeException localError)
  10. {
  11. throw localError;
  12. }
  13. catch (Throwable localThrowable)
  14. {
  15. throw new UndeclaredThrowableException(localThrowable);
  16. }
  17. }

核心就在于this.h.invoke(this. m3, null);此处的h是啥呢?我们看看这个类的类名:

public final class $Proxy0 extends Proxy implements UserService

不难发现,新生成的这个类,继承了Proxy类实现了UserService这个方法,而这个UserService就是我们指定的接口,所以,这里我们基本可以断定,JDK的动态代理,生成的新代理类就是继承了Proxy基类,实现了传入的接口的类。那这个h到底是啥呢?我们再看看这个新代理类,看看构造函数:

  1. public $Proxy0(InvocationHandler paramInvocationHandler)
  2. throws
  3. {
  4. super(paramInvocationHandler);
  5. }

构造函数里传入了一个InvocationHandler类型的参数,看到这里,我们就应该想到之前的一行代码:

return cons.newInstance(new Object[]{h});

这是newInstance方法的最后一句,传入的h,就是这里用到的h,也就是我们最初自己定义的MyInvocationHandler类的实例。所以,我们发现,其实最后调用的add()方法,其实调用的是MyInvocationHandler的invoke()方法。我们再来看一下这个方法,找一下m3的含义,继续看代理类的源码:

  1. static
  2. {
  3. try
  4. {
  5. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
  6. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  7. m3 = Class.forName("com.adam.java.basic.UserService").getMethod("add", new Class[0]);
  8. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  9. return;
  10. }

惊喜的发现,原来这个m3,就是原接口的add()方法,看到这里,还有什么不明白的呢?我想2,3,4问题都应该迎刃而解了吧?我们继续,看看原始MyInvocationHandler里的invoke()方法:

  1. <span style="white-space:pre">  </span>@Override
  2. public Object invoke(Object proxy, Method method, Object[] args)
  3. throws Throwable {
  4. System.out.println("----- before -----");
  5. Object result = method.invoke(target, args);
  6. System.out.println("----- after -----");
  7. return result;
  8. }

m3就是将要传入的method,所以,为什么先输出before,后输出after,到这里是不是全明白了呢?这,就是JDK的动态代理整个过程,不难吧?

最后,我稍微总结一下JDK动态代理的操作过程:

1. 定义一个接口,该接口里有需要实现的方法,并且编写实际的实现类。

2. 定义一个InvocationHandler类,实现InvocationHandler接口,重写invoke()方法,且添加getProxy()方法。

总结一下动态代理实现过程:

1. 通过getProxyClass0()生成代理类。

2. 通过Proxy.newProxyInstance()生成代理类的实例对象,创建对象时传入InvocationHandler类型的实例。

3. 调用新实例的方法,即此例中的add(),即原InvocationHandler类中的invoke()方法。

好了,写了这么多,也该结尾了,感谢博友Rejoy的一篇文章,让我有了参考。同时欢迎大家一起提问讨论,如有问题,请留言,我会抽空回复。相关代码已经上传至百度网盘,下载地址

联系方式:

邮箱:xtfggef@gmail.com

微博:http://weibo.com/xtfggef

Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理的更多相关文章

  1. Java之美&lbrack;从菜鸟到高手演变&rsqb;系列之博文阅读导航

    随着博文越来越多,为博客添加一个导航很有必要!本博客将相继开通Java.CloudFoundry.Linux.Ruby等专栏,都会设立目录,希望读者朋友们能更加方便的阅读! 在阅读的过程中有任何问题, ...

  2. Java之美&lbrack;从菜鸟到高手演变&rsqb;之设计模式

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  3. Java之美&lbrack;从菜鸟到高手演变&rsqb;之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  4. Java之美&lbrack;从菜鸟到高手演变&rsqb;之智力题【史上最全】 &lpar;转&rpar;

    原文地址:http://blog.csdn.net/zhangerqing/article/details/8138296 PS:在一次偶然的机会中,发现了这篇文章.希望大家能开动脑经. 智力题,每个 ...

  5. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  6. Java之美&lbrack;从菜鸟到高手演变&rsqb;之设计模式四

    在阅读过程中有任何问题,请及时联系:egg. 邮箱:xtfggef@gmail.com 微博:http://weibo.com/xtfggef 转载请说明出处:http://blog.csdn.net ...

  7. Java之美&lbrack;从菜鸟到高手演变&rsqb;之设计模式三

    本章是关于设计模式的最后一讲,会讲到第三种设计模式--行为型模式,共11种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模 ...

  8. Java之美&lbrack;从菜鸟到高手演变&rsqb;之设计模式二

    在阅读过程中有任何问题,请及时联系:egg. 邮箱:xtfggef@gmail.com 微博:http://weibo.com/xtfggef 如有转载,请说明出处:http://blog.csdn. ...

  9. Java之美&lbrack;从菜鸟到高手演变&rsqb;之字符串

    一.String 1.String简介 初始化: 一般由String声明的字符串,长度是不可变的,这也是它与StringBuffer和StringBuilder最直观的一个区别.一般初始化方式:Str ...

随机推荐

  1. Smarty缓存技术总结

    大家应该都知道合理使用缓存能有效的减轻网站的服务器压力,php Smarty作为一个非常优秀的php模板引擎,它为我们提供了非常简单而多样化的缓存操作,下面就让我们学习一下smarty缓存操作方面的一 ...

  2. 【转】android应用程序的安装方式与原理

    四种安装方式: 1.系统应用安装――开机时完成,没有安装界面 2.网络下载应用安装――通过market应用完成,没有安装界面 3.ADB工具安装――没有安装界面. 4.第三方应用安装――通过SD卡里的 ...

  3. 22个值得收藏的Android开源代码-UI篇

    本文介绍了android开发者中比较热门的开源代码,这些代码绝大多数可以直接应用到项目中. FileBrowserView 一个强大的文件选择控件.界面比较漂亮,使用也很简单.特点:可以自定义UI:支 ...

  4. ENVI如何打开IRS P6的AWIFS的ges及LISS3的ges文件?

    AWIFS文件下文件名如下:ID2010236001.hdrID2010236001_2.gesID2010236001_3.gesID2010236001_4.gesID2010236001_5.g ...

  5. 【每日一摩斯】-【序列】-续-RAC and Sequences &lpar;853652&period;1&rpar;

    一个简单的sequence有什么可以说的呢?如果再这样认为就大错特错了... 也许以下几点高人们都很清楚,但至少对于我来说,之前是陌生的,或者说是忽略的. 1.CREATE SEQUENCE seq; ...

  6. 4&lowbar;Linux&lowbar;文件压缩和解压指令

    3.4压缩解压命令.gz .tar.gz .zip .bz2 1)gzip 仅压缩文件 gzip命令用于压缩文件,英文原意为GNU zip,所在路径/bin/gzip,其语法格式为: gzip [文件 ...

  7. Promise的用法

    promise.then().promise.catch().Promise.all()... Promise 构造函数接受一个函数作为参数,该函数的2个参数分别是 resolve 和 reject. ...

  8. 聊一聊顺序消息(RocketMQ顺序消息的实现机制)

    当我们说顺序时,我们在说什么? 日常思维中,顺序大部分情况会和时间关联起来,即时间的先后表示事件的顺序关系. 比如事件A发生在下午3点一刻,而事件B发生在下午4点,那么我们认为事件A发生在事件B之前, ...

  9. 运行caffe自带的mnist实例教程

    运行caffe自带的mnist实例教程 本文结合几篇博文总结下来的,附上其中一篇原博文链接以供参考:http://blog.sina.com.cn/s/blog_168effc7e0102xjr1.h ...

  10. Python把两个列表合成一个字典

    简单粗暴上代码 A= [] B = [] C= dict(map(lambda x,y:[x,y],A,B)) 酱紫,就合成了一个字典