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之美[从菜鸟到高手演变]系列之博文阅读导航

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

  2. Java之美[从菜鸟到高手演变]之设计模式

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

  3. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

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

  4. Java之美[从菜鸟到高手演变]之智力题【史上最全】 (转)

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

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

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

  6. Java之美[从菜鸟到高手演变]之设计模式四

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

  7. Java之美[从菜鸟到高手演变]之设计模式三

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

  8. Java之美[从菜鸟到高手演变]之设计模式二

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

  9. Java之美[从菜鸟到高手演变]之字符串

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

随机推荐

  1. docker gitlab

    Alternatively, you can manually launch the gitlab container and the supporting postgresql and redis ...

  2. Sqlserver列出所有数据库名,表名,字段名

    Sqlserver列出所有数据库名,表名,字段名   1.获取所有数据库名:   ? 1 SELECT Name FROM Master..SysDatabases ORDER BY Name   注 ...

  3. 解决xfce4桌面图标消失的问题

    问题表现:panel还在,桌面的背景变成灰色,桌面图标消失,桌面右键没反映 解决方法:从登录管理器中登录另一个用户,发现桌面正常,猜测是由于家目录下的配置文件引起的. 删除 ~/.cache/sess ...

  4. zw版【转发·台湾nvp系列Delphi例程】HALCON FillUp1

    zw版[转发·台湾nvp系列Delphi例程]HALCON FillUp1 procedure TForm1.Button1Click(Sender: TObject);var img : HImag ...

  5. yii2中表单的字段标签名称

    1.以登陆页面为例,默认是英文的,在loginForm.php中添加attributeLabels,可以变成中文 具体代码如下: public function attributeLabels(){  ...

  6. yii2表关联实例

    yii2表关联 1.两张表关联,以“商品表关联品牌表”为例 控制器中: $goods_model=new Goods(); $goods_info=$goods_model::find()->j ...

  7. opencv之深拷贝及浅拷贝,IplImage装换为Mat

    一.(1)  浅拷贝: Mat B; B = image  // 第一种方式 Mat C(image); // 第二种方式 这两种方式称为浅copy,是由于它们有不同的矩阵头,但是它们共享内存空间,即 ...

  8. python爬虫框架scrapy实例详解

    生成项目scrapy提供一个工具来生成项目,生成的项目中预置了一些文件,用户需要在这些文件中添加自己的代码.打开命令行,执行:scrapy st... 生成项目 scrapy提供一个工具来生成项目,生 ...

  9. 危险的 SQL

    看下这个 SQL , 有什么问题 ? <update id="update" parameterType="CreativeGroupDO"> up ...

  10. REDHAT4.8安装yum

    公司用的机器还在跑RHEL4(Red Hat Enterprise Linux AS 4.8),没有yum安装软件真是费时间啊,于是上网找了半天yum安装方法,终于成功.分享一下:核心:wget ht ...