在开始动态代理的描述之前,让我们认识下代理。代理:即代替担任执行职务。在面向对象世界中,即寻找另一个对象代理目标对象与调用者交互。Java中分为静态代理和动态代理。这里对于静态代理不做详述。它们之间的区别,即前者是编译时生成代理对象,后者在运行时生成代理对象,体现一静一动。

一.前言

Java中实现动态代理的技术非常繁多,目前主流技术是以下三种:

  • JDK动态代理
  • CGLIB
  • Javassist

JDK动态代理和CGLIB在Spring AOP中有所使用,Javassit在Hibernate中有所使用。当然还有很多其他的动态代理实现技术:ASM、Byte Buddy、JiteScript、Proxetta等等。这里只对JDK动态代理和CGLIB做详细说明。关于其他代理技术的介绍可以参考 Are there alternatives to cglib?

本篇文章只对JDK动态代理的实现做深入了解,CGLIB原理后续文章再做详细介绍。

二.实现动态代理的Demo

创建JDK动态代理可以分为以下几个步骤:

  • 创建被代理对象的接口
  • 创建被代理对象
  • 创建InvocationHandler
  • 创建代理对象并调用
1.创建被代理对象的接口
  1. public interface EchoService {
  2. void echo(String message);
  3. }
2. 创建被代理对象
  1. public class EchoServiceImpl implements EchoService {
  2. @Override
  3. public void echo(String message) {
  4. System.out.println(message);
  5. }
  6. }
3. 创建InvocationHandler
  1. public class DefaultCommonHandler implements InvocationHandler {
  2. private Object object;
  3. public DefaultCommonHandler(Object o) {
  4. this.object = o;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("before invoke");
  9. return method.invoke(object, args);
  10. }
  11. }
4.创建代理对象
  1. public class JdkDynamicProxyTest {
  2. public static void main(String[] args) throws IOException {
  3. InvocationHandler handler = new DefaultCommonHandler(new EchoServiceImpl());
  4. EchoService proxyObject = (EchoService) Proxy
  5. .newProxyInstance(EchoService.class.getClassLoader(), new Class[]{EchoService.class}, handler);
  6. proxyObject.echo("invoking");
  7. }
  8. }

上述执行结果:

before invoke

invoking

从以上的编写过程和执行结果中,应该大致的感受到动态代理的运行逻辑,接下来深入理解其原理。

三.深入认知原理前的基础准备

在进一步深入认知JDK动态代理原理之前,先来学习下JDK动态代理实现的基础。Java中提供两个api:Proxy类和InvocationHandler调用委派处理器,用于实现JDK动态代理。

先来看下Proxy的Java docs描述:

{@code Proxy} provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods

从描述中可以看出,Proxy有以下特点:

  • Proxy提供了静态方法用于运行时创建代理类的class对象和代理类的实例对象
  • Proxy是所有的动态代理类的超类

Proxy提供了两种方式创建代理类的对象:

1.先创建代理的类的class对象,然后通过class对象获取构造函数,通过构造函数对象Constructor创建实例对象
  1. InvocationHandler handler = new MyInvocationHandler(...);
  2. Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(),
  3. new Class[] { Foo.class });
  4. Foo f = (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class })
  5. .newInstance(new Object[] { handler });
2. 通过Proxyy停的newProxyInstance方法直接创建代理类的实例。本质上也是上面的过程。
  1. Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
  2. new Class[] { Foo.class }, handler);

本篇文章主要目的是介绍动态代理的原理,不是对Proxy的具体实现细节做深入了解。这里不再赘述Proxy的细节。

再来看下InvocationHandler。InvocationHandler是动态代理实现的关键,代理模式中的扩展点就是由其承担的。代理的逻辑都是从InvocationHandler中的invoke方法实现。首先看下Java docs描述:

{@code InvocationHandler} is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance,the method invocation is encoded and dispatched to the {@code invoke} method of its invocation handler

从描述中得知,InvocationHandler与代理类的实例有关,每个代理类中都持有一个实现InvocationHandler接口的实例。代理类上的方法的调用将被组织和委派到InvocationHandler的统一方法invoke上。

下面从源码上简单介绍下上述的实现:

  1. /**
  2. * the invocation handler for this proxy instance.
  3. * @serial
  4. */
  5. protected InvocationHandler h;
  6. /**
  7. * Prohibits instantiation.
  8. */
  9. private Proxy() {
  10. }
  11. /**
  12. * Constructs a new {@code Proxy} instance from a subclass
  13. * (typically, a dynamic proxy class) with the specified value
  14. * for its invocation handler.
  15. *
  16. * @param h the invocation handler for this proxy instance
  17. */
  18. protected Proxy(InvocationHandler h) {
  19. doNewInstanceCheck();
  20. this.h = h;
  21. }

以上代码片段是Proxy中的成员域和构造方法。Proxy类中持有InvocationHandler实例,且封装访问为protected,并在Proxy的构造器中初始化。前面提到所有的代理类都继承了Proxy类,所以代理类中拥有InvocationHandler实例,并在代理类初始化时调用父类Proxy的构造器对InvocationHandler进行赋值。

四.分析代理对象中的细节

如果要分析代理对象的细节,必然需要阅读代理对象中代码实现。

  1. 可以阅读字节码,但是可读性太低且技术门槛过高
  2. 反编译代理对象的.class文件

以上两种方式都需要获取代理对象的.class字节码文件。Java中提供了参数配置生成代理对象的class文件。

Java中生成class文件方法是ProxyGenerator,生成代码如下:

  1. public static byte[] generateProxyClass(final String var0, Class[] var1) {
  2. ProxyGenerator var2 = new ProxyGenerator(var0, var1);
  3. final byte[] var3 = var2.generateClassFile();
  4. if(saveGeneratedFiles) {
  5. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  6. public Void run() {
  7. try {
  8. FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
  9. var1.write(var3);
  10. var1.close();
  11. return null;
  12. } catch (IOException var2) {
  13. throw new InternalError("I/O exception saving generated file: " + var2);
  14. }
  15. }
  16. });
  17. }
  18. return var3;
  19. }

主要是if条件中参数决定是否生成代理对象的.class文件:

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

从JDK源代码中可以分析出只要,设置参数sun.misc.ProxyGenerator.saveGeneratedFiles即可。从这里可以看出只要设置即可:

  1. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

Boolean类中的getBoolean如下:

  1. public static boolean getBoolean(String name) {
  2. boolean result = false;
  3. try {
  4. result = toBoolean(System.getProperty(name));
  5. } catch (IllegalArgumentException e) {
  6. } catch (NullPointerException e) {
  7. }
  8. return result;
  9. }

从以上生成.class文件的generateProxyClass方法中可以看出,使用的是FileOutputStream文件输出流,文件的输出目录需要预先创建 好,否则将会抛出异常FileNotFoundException

在准备生成好.class文件后,下面详细分析生成的代理类。生成的代理类类名都是以$Proxy0开头,如:$Proxy0.class。

如下就是代理类反编译后的代码:

  1. package com.sun.proxy;
  2. import com.java.proxy.api.EchoService;
  3. import java.lang.reflect.InvocationHandler;
  4. import java.lang.reflect.Method;
  5. import java.lang.reflect.Proxy;
  6. import java.lang.reflect.UndeclaredThrowableException;
  7. public final class $Proxy0 extends Proxy implements EchoService {
  8. private static Method m1;
  9. private static Method m3;
  10. private static Method m0;
  11. private static Method m2;
  12. public $Proxy0(InvocationHandler var1) throws {
  13. super(var1);
  14. }
  15. public final boolean equals(Object var1) throws {
  16. try {
  17. return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
  18. } catch (RuntimeException | Error var3) {
  19. throw var3;
  20. } catch (Throwable var4) {
  21. throw new UndeclaredThrowableException(var4);
  22. }
  23. }
  24. public final void echo(String var1) throws {
  25. try {
  26. super.h.invoke(this, m3, new Object[]{var1});
  27. } catch (RuntimeException | Error var3) {
  28. throw var3;
  29. } catch (Throwable var4) {
  30. throw new UndeclaredThrowableException(var4);
  31. }
  32. }
  33. public final int hashCode() throws {
  34. try {
  35. return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
  36. } catch (RuntimeException | Error var2) {
  37. throw var2;
  38. } catch (Throwable var3) {
  39. throw new UndeclaredThrowableException(var3);
  40. }
  41. }
  42. public final String toString() throws {
  43. try {
  44. return (String)super.h.invoke(this, m2, (Object[])null);
  45. } catch (RuntimeException | Error var2) {
  46. throw var2;
  47. } catch (Throwable var3) {
  48. throw new UndeclaredThrowableException(var3);
  49. }
  50. }
  51. static {
  52. try {
  53. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
  54. m3 = Class.forName("com.java.proxy.api.EchoService").getMethod("echo", new Class[]{Class.forName("java.lang.String")});
  55. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  56. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  57. } catch (NoSuchMethodException var2) {
  58. throw new NoSuchMethodError(var2.getMessage());
  59. } catch (ClassNotFoundException var3) {
  60. throw new NoClassDefFoundError(var3.getMessage());
  61. }
  62. }
  63. }

往往探知究竟还是需要阅读源码。这里我们使用这种看起来土但是却是无比简单易用的方式来分析JDK生成的代理类的细节。

这里最让人关注无非就是动态代理类的调用时怎样委派到最终实际的被代理对象上的这个过程的。在阅读source前,先直观的感受下调用过程图:

从图中看出,可以将整个调用过程分为几个部分:

  • 调用者对象
  • 代理对象
  • 实现invocationHandler对象
  • 被代理对象

调用者invoke代理对象,代理对象进行组织和委派给InvocationHandler的统一接口invoke方法,invocationHandler中invoke通过反射的方式调用被代理对象的方法。

下面再来分析下动态生成的代理类的基本特点:

  • 代理类集成Proxy类且实现创建代理对象时的列表中的接口
  • 代理类是final,不能再被继承
  • 代理类中持有InvcationHandler的实例
  • 代理类对Object中的toString、hashCode、equals方法进行了代理,对构造时的接口中的所有方法进行了代理
  • 代理使用静态语句块,利用反射获取了这些方法的描述对象Method。在创建代理类实例时就已经将这些方法解析,toString、hashCode、equals一般是前三个方法m0、m1、m2
  • 代理类中生成的代理方法都是final
  • 代理类不会对被代理对象中的final,private,protected方法进行代理
  • 代理类名以$Proxy开头

上面提到InvocationHandler的统一接口invoke,这个是至关重要的点。代理对象到别代理对象的过渡就是通过这个方法委派,下面看下这个接口的定义:

  1. public Object invoke(Object proxy, Method method, Object[] args)
  2. throws Throwable;

第一个参数是代理对象,第二个参数是调用者调用的被代理的代理方法,第三个参数是被代理方法的参数。

如果代理的所有接口中有重复方法,则取第一个方法。

参考

Dynamic Proxy Classes

dynamic-proxy

Are there alternatives to cglib?

动态代理(一)——JDK中的动态代理的更多相关文章

  1. 动态代理的两种方式,以及区别(静态代理、JDK与CGLIB动态代理、AOP+IoC)

    Spring学习总结(二)——静态代理.JDK与CGLIB动态代理.AOP+IoC   目录 一.为什么需要代理模式 二.静态代理 三.动态代理,使用JDK内置的Proxy实现 四.动态代理,使用cg ...

  2. 通过模拟JDK中的动态代理,由浅入深讲解动态代理思想.

    目录 场景引入 动态代理引入 动态代理进阶 总结 个人认为动态代理在设计模式中算是比较难的, 本篇文章将从无到有, 从一个简单代码示例开始迭代, 逐步深入讲解动态代理思想. 场景引入 假设现在有一个坦 ...

  3. Spring学习总结(二)——静态代理、JDK与CGLIB动态代理、AOP+IoC

    一.为什么需要代理模式 假设需实现一个计算的类Math.完成加.减.乘.除功能,如下所示: package com.zhangguo.Spring041.aop01; public class Mat ...

  4. 总结两种动态代理jdk代理和cglib代理

    动态代理 上篇文章讲了什么是代理模式,为什么用代理模式,从静态代理过渡到动态代理. 这里再简单总结一下 什么是代理模式,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原 ...

  5. java设计模式中的动态代理

    Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩 ...

  6. java动态代理(JDK和cglib)(转载)

    原文地址:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html 高亮部分是我的理解. JAVA的动态代理 代理模式 代理模式是常用的j ...

  7. 代理模式-jdk动态代理

    IDB package com.bjpowernode.proxy; /** * 代理类和目标类都必须使用同一个接口. */ public interface IDB { int insert(); ...

  8. 静态代理和动态代理(jdk/cglib)详解

    1.静态代理模式 代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色.其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口:RealSu ...

  9. Java 代理模式 (二) 动态代理

    代理模式 代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式:即通过代理访问目标对象. 这样好处: 可以在目标对象实现的基础上,增强额外的功能操作.(扩展目标对象的功能). 代理模式的 ...

随机推荐

  1. 锁、分布式锁、无锁实战全局性ID

    1.为什么要使用锁 当发生并发时,会产生多线程争夺一个资源,为保证资源的唯一性. JVM锁:对象锁,死锁,重入锁,公平锁,偏向锁 分布式锁:数据库 nosql .zookeeper 面试题:如何排查死 ...

  2. 8皇后问题SQL求解(回溯算法)

    问题 八皇后问题是一个古老而著名的问题,是回溯算法的典型例题.该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一 ...

  3. 蓝色映象 幻舞少女之剑 BLUE REFLECTION 后感

    到底是看片收获多还是游戏收获多?在刷蓝色反射的时候刷了2部番.所以,我到底是为了什么在玩游戏呢? 岸田メル的人设,毋庸置疑,唯美想舔,且总能给人一种绝无杂质,纯洁治愈的感觉,再加上浅野隼人的配乐,恰如 ...

  4. Codeforces Round #579 (Div. 3)

    Codeforces Round #579 (Div. 3) 传送门 A. Circle of Students 这题我是直接把正序.逆序的两种放在数组里面直接判断. Code #include &l ...

  5. 第07节-开源蓝牙协议BTStack框架代码阅读(下)

    上篇博客中已经对BTStack框架进行了较为详细的说明,本篇博客将进一步总结一下(由韦大仙笔记所得). 可以从5个方面来理解BTStack的框架: 1.硬件操作:hci_transport_t BTS ...

  6. arXiv上传文章latex源码技巧

    <<2019.09.27>>更新 上传PS文件看来也是不行了,一大早收到邮件被arXiv标记为incomplete了.哎,还是老老实实提交Latex source files吧 ...

  7. 如何用<dl>标签做表格而不用table标签

    我们都知道很多的内容编辑器(TinyMCE编辑器.fck)都有插入表格功能,快速方便,但是这些表格用到的<table>标签,可以查看html源代码就能发现,table标签对搜索引擎不是很友 ...

  8. MySQL 事务配置命令行操作和持久化

    MySQL 事务配置命令行操作和持久化 参考 MySQL 官方参考手册 提供 5.5 5.6 5.7 8.0 版本的参考手册 https://dev.mysql.com/doc/refman/5.5/ ...

  9. scrapy框架--?乱码unicode

    安装 pip install scrapy 建立一个爬虫项目 scrapy startproject 项目名称 scrapy startproject itcast 进入itcast文件夹 生成一个爬 ...

  10. yarn rest api未授权漏洞

    项目集群之前在yarn8088的web资源管理页面上看到一堆莫名的定时任务,就是黑客利用漏洞挖矿,最后禁用了8088端口. freebuff传送门: https://www.freebuf.com/v ...