原文:http://blog.csdn.net/mhmyqn/article/details/48474815

关于JDK的动态代理,最为人熟知的可能要数Spring AOP的实现,默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。那么,什么是JDK的动态代理呢?

JDK的动态代理,就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程。JDK从1.3开始支持动态代理。那么JDK是如何生成动态代理的呢?JDK动态代理为什么不支持类的代理,只支持接口的代理?

首先来看一下如何使用JDK动态代理。JDK提供了java.lang.reflect.Proxy类来实现动态代理的,可通过它的newProxyInstance来获得代理实现类。同时对于代理的接口的实际处理,是一个java.lang.reflect.InvocationHandler,它提供了一个invoke方法供实现者提供相应的代理逻辑的实现。可以对实际的实现进行一些特殊的处理,像Spring AOP中的各种advice。下面来看看如何使用。

被代理的接口

package com.mikan.proxy;  

/**
* @author Mikan
* @date 2015-09-15 18:00
*/
public interface HelloWorld { void sayHello(String name); }

  接口的实现类:

  1. package com.mikan.proxy;
  2. /**
  3. * @author Mikan
  4. * @date 2015-09-15 18:01
  5. */
  6. public class HelloWorldImpl implements HelloWorld {
  7. @Override
  8. public void sayHello(String name) {
  9. System.out.println("Hello " + name);
  10. }
  11. }

实现一个java.lang.reflect.InvocationHandler:

  1. package com.mikan.proxy;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. /**
  5. * @author Mikan
  6. * @date 2015-09-15 19:53
  7. */
  8. public class CustomInvocationHandler implements InvocationHandler {
  9. private Object target;
  10. public CustomInvocationHandler(Object target) {
  11. this.target = target;
  12. }
  13. @Override
  14. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  15. System.out.println("Before invocation");
  16. Object retVal = method.invoke(target, args);
  17. System.out.println("After invocation");
  18. return retVal;
  19. }
  20. }

使用代理:

  1. package com.mikan.proxy;
  2. import java.lang.reflect.Proxy;
  3. /**
  4. * @author Mikan
  5. * @date 2015-09-15 18:01
  6. */
  7. public class ProxyTest {
  8. public static void main(String[] args) throws Exception {
  9. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  10. CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());
  11. HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
  12. ProxyTest.class.getClassLoader(),
  13. new Class[]{HelloWorld.class},
  14. handler);
  15. proxy.sayHello("Mikan");
  16. }
  17. }

运行的输出结果:

  1. localhost:classes mikan$ java com/mikan/proxy/ProxyTest
  2. Before invocation
  3. Hello Mikan
  4. After invocation

从上面可以看出,JDK的动态代理使用起来非常简单,但是只知道如何使用是不够的,知其然,还需知其所以然。所以要想搞清楚它的实现,那么得从源码入手。这里的源码是1.7.0_79。首先来看看它是如何生成代理类的:

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h)
  4. throws IllegalArgumentException {
  5. if (h == null) {
  6. throw new NullPointerException();
  7. }
  8. final Class<?>[] intfs = interfaces.clone();
  9. final SecurityManager sm = System.getSecurityManager();
  10. if (sm != null) {
  11. checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  12. }
  13. // 这里是生成class的地方
  14. Class<?> cl = getProxyClass0(loader, intfs);
  15. // 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例
  16. try {
  17. final Constructor<?> cons = cl.getConstructor(constructorParams);
  18. final InvocationHandler ih = h;
  19. if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
  20. return AccessController.doPrivileged(new PrivilegedAction<Object>() {
  21. public Object run() {
  22. return newInstance(cons, ih);
  23. }
  24. });
  25. } else {
  26. return newInstance(cons, ih);
  27. }
  28. } catch (NoSuchMethodException e) {
  29. throw new InternalError(e.toString());
  30. }
  31. }

其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:

  1. private static Class<?> getProxyClass0(ClassLoader loader,
  2. Class<?>... interfaces) {
  3. // 代理的接口数量不能超过65535(没有这种变态吧)
  4. if (interfaces.length > 65535) {
  5. throw new IllegalArgumentException("interface limit exceeded");
  6. }
  7. // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理
  8. return proxyClassCache.get(loader, interfaces);
  9. }

其中代理缓存是使用WeakCache实现的,如下

  1. private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
  2. proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的apply方法:

  1. private static final class ProxyClassFactory
  2. implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
  3. // 所有代理类名字的前缀
  4. private static final String proxyClassNamePrefix = "$Proxy";
  5. // 用于生成代理类名字的计数器
  6. private static final AtomicLong nextUniqueNumber = new AtomicLong();
  7. @Override
  8. public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  9. // 省略验证代理接口的代码……
  10. String proxyPkg = null;     // 生成的代理类的包名
  11. // 对于非公共接口,代理类的包名与接口的相同
  12. for (Class<?> intf : interfaces) {
  13. int flags = intf.getModifiers();
  14. if (!Modifier.isPublic(flags)) {
  15. String name = intf.getName();
  16. int n = name.lastIndexOf('.');
  17. String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
  18. if (proxyPkg == null) {
  19. proxyPkg = pkg;
  20. } else if (!pkg.equals(proxyPkg)) {
  21. throw new IllegalArgumentException(
  22. "non-public interfaces from different packages");
  23. }
  24. }
  25. }
  26. // 对于公共接口的包名,默认为com.sun.proxy
  27. if (proxyPkg == null) {
  28. proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  29. }
  30. // 获取计数
  31. long num = nextUniqueNumber.getAndIncrement();
  32. // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增
  33. String proxyName = proxyPkg + proxyClassNamePrefix + num;
  34. // 这里才是真正的生成代理类的字节码的地方
  35. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  36. proxyName, interfaces);
  37. try {
  38. // 根据二进制字节码返回相应的Class实例
  39. return defineClass0(loader, proxyName,
  40. proxyClassFile, 0, proxyClassFile.length);
  41. } catch (ClassFormatError e) {
  42. throw new IllegalArgumentException(e.toString());
  43. }
  44. }
  45. }

ProxyGenerator是sun.misc包中的类,它没有开源,但是可以反编译来一探究竟:

  1. public static byte[] generateProxyClass(final String var0, Class[] var1) {
  2. ProxyGenerator var2 = new ProxyGenerator(var0, var1);
  3. final byte[] var3 = var2.generateClassFile();
  4. // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观
  5. if(saveGeneratedFiles) {
  6. AccessController.doPrivileged(new PrivilegedAction() {
  7. public Void run() {
  8. try {
  9. FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
  10. var1.write(var3);
  11. var1.close();
  12. return null;
  13. } catch (IOException var2) {
  14. throw new InternalError("I/O exception saving generated file: " + var2);
  15. }
  16. }
  17. });
  18. }
  19. return var3;
  20. }

saveGeneratedFiles这个属性的值从哪里来呢:

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

GetBooleanAction实际上是调用Boolean.getBoolean(propName)来获得的,而Boolean.getBoolean(propName)调用了System.getProperty(name),所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。

这里要注意,当把这个属性设置为true时,生成的class文件及其所在的路径都需要提前创建,否则会抛出FileNotFoundException异常。如:

  1. Exception in thread "main" java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException: com/sun/proxy/$Proxy0.class (No such file or directory)
  2. at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336)
  3. at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327)
  4. at java.security.AccessController.doPrivileged(Native Method)
  5. at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326)
  6. at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:672)
  7. at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592)
  8. at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244)
  9. at java.lang.reflect.WeakCache.get(WeakCache.java:141)
  10. at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:455)
  11. at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738)
  12. at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15)
  13. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  14. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  15. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  16. at java.lang.reflect.Method.invoke(Method.java:606)
  17. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。

反编译$Proxy0.class文件,如下所示:

  1. package com.sun.proxy;
  2. import com.mikan.proxy.HelloWorld;
  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 HelloWorld {
  8. private static Method m1;
  9. private static Method m3;
  10. private static Method m0;
  11. private static Method m2;
  12. public $Proxy0(InvocationHandler paramInvocationHandler) {
  13. super(paramInvocationHandler);
  14. }
  15. public final boolean equals(Object paramObject) {
  16. try {
  17. return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  18. }
  19. catch (Error|RuntimeException localError) {
  20. throw localError;
  21. }
  22. catch (Throwable localThrowable) {
  23. throw new UndeclaredThrowableException(localThrowable);
  24. }
  25. }
  26. public final void sayHello(String paramString) {
  27. try {
  28. this.h.invoke(this, m3, new Object[] { paramString });
  29. return;
  30. }
  31. catch (Error|RuntimeException localError) {
  32. throw localError;
  33. }
  34. catch (Throwable localThrowable) {
  35. throw new UndeclaredThrowableException(localThrowable);
  36. }
  37. }
  38. public final int hashCode() {
  39. try {
  40. return ((Integer)this.h.invoke(this, m0, null)).intValue();
  41. }
  42. catch (Error|RuntimeException localError) {
  43. throw localError;
  44. }
  45. catch (Throwable localThrowable) {
  46. throw new UndeclaredThrowableException(localThrowable);
  47. }
  48. }
  49. public final String toString() {
  50. try {
  51. return (String)this.h.invoke(this, m2, null);
  52. }
  53. catch (Error|RuntimeException localError) {
  54. throw localError;
  55. }
  56. catch (Throwable localThrowable) {
  57. throw new UndeclaredThrowableException(localThrowable);
  58. }
  59. }
  60. static {
  61. try {
  62. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
  63. m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
  64. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  65. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  66. return;
  67. }
  68. catch (NoSuchMethodException localNoSuchMethodException) {
  69. throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
  70. }
  71. catch (ClassNotFoundException localClassNotFoundException) {
  72. throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  73. }
  74. }
  75. }

可以看到,动态生成的代理类有如下特性:

  1. 继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
  2. 提供了一个使用InvocationHandler作为参数的构造方法。
  3. 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。
  4. 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
  5. 代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

至此JDK动态代理的实现原理就分析的差不多了。同时我们可以想像一下Spring AOP提供的各种拦截该如何实现,就已经很明了了,如下所示:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. // BeforeAdvice
  3. Object retVal = null;
  4. try {
  5. // AroundAdvice
  6. retVal = method.invoke(target, args);
  7. // AroundAdvice
  8. // AfterReturningAdvice
  9. }
  10. catch (Throwable e) {
  11. // AfterThrowingAdvice
  12. }
  13. finally {
  14. // AfterAdvice
  15. }
  16. return retVal;
  17. }

上面是对于Spring AOP使用JDK动态代理实现的基本框架代码,当然具体的实现肯定比这个复杂得多,但是基本原理不外乎如是。所以理解基本原理对于理解其他的代码也是很有好处的。

(转)细说JDK动态代理的实现原理的更多相关文章

  1. JDK动态代理的实现原理

    学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504

  2. jdk动态代理使用及原理

    jdk动态代理的使用 1.创建实现InvocationHandler接口的类,实现invoke(Object proxy, Method method, Object[] args)接口,其中invo ...

  3. JDK动态代理案例与原理分析

    一.JDK动态代理实现案例 Person接口 package com.zhoucong.proxy.jdk; public interface Person { // 寻找真爱 void findlo ...

  4. jdk动态代理与cglib代理、spring aop代理实现原理

    原创声明:本博客来源与本人另一博客[http://blog.csdn.net/liaohaojian/article/details/63683317]原创作品,绝非他处摘取 代理(proxy)的定义 ...

  5. jdk动态代理与cglib代理、spring aop代理实现原理解析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  6. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  7. 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)

    代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...

  8. jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  9. 解析JDK动态代理实现原理

    JDK动态代理使用实例 代理模式的类图如上.关于静态代理的示例网上有很多,在这里就不讲了. 因为本篇讲述要点是JDK动态代理的实现原理,直接从JDK动态代理实例开始. 首先是Subject接口类. p ...

随机推荐

  1. POJ2516 Minimum Cost【最小费用最大流】

    题意: 有N个客户,M个仓库,和K种货物.已知每个客户需要每种货物的数量,每个仓库存储每种货物的数量,每个仓库运输各种货物去各个客户的单位费用.判断所有的仓库能否满足所有客户的需求,如果可以,求出最少 ...

  2. include的作用

    #include发生在预处理阶段,整个编译链接过程,#include是最简单的了,没有之一.就是在include的位置直接把文件原原本本完完整整一字不落的包含进来,下面举一个极端点的例子: //fil ...

  3. More Effective C++ 条款0,1

    More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类 ...

  4. Android 常用 adb 命令总结【转】

    原文链接 针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb 命令熟记于心, 将会为 Android 测试带来很大的方便,其中很多命令将会用于自动化测试的脚本当中 ...

  5. SpringMVC与Struts不同(五)

    springmvc与struts2不同 1.springmvc的入口是一个servlet即前端控制器,而struts2入口是一个filter过滤器. 2.springmvc是基于方法开发(一个url对 ...

  6. host-only

    https://www.cnblogs.com/yaox/p/6635312.html

  7. Netty+SpringBoot写一个基于Http协议的文件服务器

    本文参考<Netty权威指南> NettyApplication package com.xh.netty; import org.springframework.boot.SpringA ...

  8. Java探针-Java Agent技术-阿里面试题

    Java探针参考:Java探针技术在应用安全领域的新突破 最近面试阿里,面试官先是问我类加载的流程,然后问了个问题,能否在加载类的时候,对字节码进行修改 我懵逼了,答曰不知道,面试官说可以的,使用Ja ...

  9. python 历险记(四)— python 中常用的 json 操作

    目录 引言 基础知识 什么是 JSON? JSON 的语法 JSON 对象有哪些特点? JSON 数组有哪些特点? 什么是编码和解码? 常用的 json 操作有哪些? json 操作需要什么库? 如何 ...

  10. centos常用网络管理命令

    网卡配置命令:ifconfig (ip addr , ip link) ifconfig:显示所有活动状态的相关信息    ifconfig Interface:仅显示指定接口的相关信息    ifc ...