Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。通过阅读本文,读者将会对 Java 动态代理机制有更加深入的理解。本文首先从 Java 动态代理的运行机制和特点出发,对其代码进行了分析,推演了动态生成类的内部实现。

代理设计模式

定义:为其他对象提供一种代理以控制对这个对象的访问。代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代理模式图:

java动态代理机制以巧妙的方式实现了代理模式的设计理念。

相关的类和接口

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

  • java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

    1.Proxy 的静态方法

      1 // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    2 static InvocationHandler getInvocationHandler(Object proxy)
    3
    4 // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    5 static Class getProxyClass(ClassLoader loader, Class[] interfaces)
    6
    7 // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    8 static boolean isProxyClass(Class cl)
    9
    10 // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    11 static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
    12 InvocationHandler h)
  • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
    2.InvocationHandler 的核心方法

      1 // 该方法负责集中处理动态代理类上的所有方法调用。  2 // 第一个参数既是代理类实例,第二个参数是被调用的方法对象
    3 // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    4 Object invoke(Object proxy, Method method, Object[] args)

    每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。

  • java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

    每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

代理机制及其特点

  • 首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:
    通过实现 InvocationHandler 接口创建自己的调用处理器;
    通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
    3. 动态代理对象创建过程
  •   1 // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    2 // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    3 InvocationHandler handler = new InvocationHandlerImpl(..);
    4
    5 // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    6 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
    7
    8 // 通过反射从生成的类对象获得构造函数对象
    9 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
    10
    11 // 通过构造函数对象创建动态代理类实例
    12 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
  • 实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了上面红色的代码过程,所以简化后的过程如下
    4. 简化的动态代理对象创建过程
  •   1 // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    2 InvocationHandler handler = new InvocationHandlerImpl(..);
    3
    4 // 通过 Proxy 直接创建动态代理类实例
    5 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
    6 new Class[] { Interface.class },
    7 handler );
  • 动态代理机制的特点:
         首先是动态生成的代理类本身的一些特点。
         1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
         2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
         3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
         4)类继承关系:该类的继承关系如图:

由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

代理机制及其特点

  • 首先记住 Proxy 的几个重要的静态变量:
    5. Proxy 的重要静态变量
  •   1 // 映射表:用于维护类装载器对象到其对应的代理类缓存
    2 private static Map loaderToCache = new WeakHashMap();
    3
    4 // 标记:用于标记一个动态代理类正在被创建中
    5 private static Object pendingGenerationMarker = new Object();
    6
    7 // 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
    8 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
    9
    10 // 关联的调用处理器引用
    11 protected InvocationHandler h;
  • Proxy 的构造方法:
    6. Proxy 构造方法和静态方法newProxyInstance

      1 // 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
    2 private Proxy() {}
    3 // 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
    4 protected Proxy(InvocationHandler h) {this.h = h;}
    5
    6
    7 public static Object newProxyInstance(ClassLoader loader,
    8
    9 Class<?>[] interfaces,
    10
    11 InvocationHandler h)
    12
    13 throws IllegalArgumentException {
    14
    15
    16
    17 // 检查 h 不为空,否则抛异常
    18
    19 if (h == null) {
    20
    21 throw new NullPointerException();
    22
    23 }
    24
    25 // 获得与制定类装载器和一组接口相关的代理类类型对象
    26
    27 Class cl = getProxyClass(loader, interfaces);
    28
    29 // 通过反射获取构造函数对象并生成代理类实例
    30
    31 try {
    32
    33 Constructor cons = cl.getConstructor(constructorParams);
    34
    35 return (Object) cons.newInstance(new Object[] { h });
    36
    37 } catch (NoSuchMethodException e) { throw new InternalError(e.toString());
    38
    39 } catch (IllegalAccessException e) { throw new InternalError(e.toString());
    40
    41 } catch (InstantiationException e) { throw new InternalError(e.toString());
    42
    43 } catch (InvocationTargetException e) { throw new InternalError(e.toString());
    44
    45 }
    46
    47 }
    48

由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类类型对象。在该方法内部,您将能看到 Proxy 内的各路英雄(静态变量)悉数登场。有点迫不及待了么?那就让我们一起走进 Proxy 最最神秘的殿堂去欣赏一番吧。该方法总共可以分为四个步骤:

  1. 对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。
  2. 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。
  3. 动态创建代理类的类对象。首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。类名也确定了,接下来就是见证奇迹的发生 —— 动态生成代理类:
  4. 由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。

走完了以上四个步骤后,至此,所有的代理类生成细节都已介绍完毕,剩下的静态方法如 getInvocationHandler 和 isProxyClass 就显得如此的直观,只需通过查询相关变量就可以完成,所以对其的代码分析就省略了。
    

  1 //通过Class.forName方法判断接口的可见性
2 try {
3 // 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
4 // 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
5 interfaceClass = Class.forName(interfaceName, false, loader);
6 } catch (ClassNotFoundException e) {
7 }==============================================================================================
8 //缓存表的使用
9 do {
10 // 以接口名字列表作为关键字获得对应 cache 值
11 Object value = cache.get(key);
12 if (value instanceof Reference) {
13 proxyClass = (Class) ((Reference) value).get();
14 }
15 if (proxyClass != null) {
16 // 如果已经创建,直接返回
17 return proxyClass;
18 } else if (value == pendingGenerationMarker) {
19 // 代理类正在被创建,保持等待
20 try {
21 cache.wait();
22 } catch (InterruptedException e) {
23 }
24 // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
25 continue;
26 } else {
27 // 标记代理类正在被创建
28 cache.put(key, pendingGenerationMarker);
29 // break 跳出循环已进入创建过程
30 break;
31 } while (true);==============================================================================================
32 //动态生成代理类
33 // 动态地生成代理类的字节码数组
34 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
35 try {
36 // 动态地定义新生成的代理类
37 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0,
38 proxyClassFile.length);
39 } catch (ClassFormatError e) {
40 throw new IllegalArgumentException(e.toString());
41 }
42
43 // 把生成的代理类的类对象记录进 proxyClasses 表
44 proxyClasses.put(proxyClass, null);

代理类实现推演

分析了 Proxy 类的源代码,相信在读者的脑海中会对 Java 动态代理机制形成一个更加清晰的理解,但是,当探索之旅在 sun.misc.ProxyGenerator 类处嘎然而止,所有的神秘都汇聚于此时,相信不少读者也会对这个 ProxyGenerator 类产生有类似的疑惑:它到底做了什么呢?它是如何生成动态代理类的代码的呢?诚然,这里也无法给出确切的答案。还是让我们带着这些疑惑,一起开始探索之旅吧。

事物往往不像其看起来的复杂,需要的是我们能够化繁为简,这样也许就能有更多拨云见日的机会。抛开所有想象中的未知而复杂的神秘因素,如果让我们用最简单的方法去实现一个代理类,唯一的要求是同样结合调用处理器实施方法的分派转发,您的第一反应将是什么呢?“听起来似乎并不是很复杂”。的确,掐指算算所涉及的工作无非包括几个反射调用,以及对原始类型数据的装箱或拆箱过程,其他的似乎都已经水到渠成。非常地好,让我们整理一下思绪,一起来完成一次完整的推演过程吧。

  1 //代理类中方法调用的分派转发推演实现
2 // 假设需代理接口 Simulator
3 public interface Simulator {
4 short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;
5 }
6
7 // 假设代理类为 SimulatorProxy, 其类声明将如下
8 final public class SimulatorProxy implements Simulator {
9
10 // 调用处理器对象的引用
11 protected InvocationHandler handler;
12
13 // 以调用处理器为参数的构造函数
14 public SimulatorProxy(InvocationHandler handler){
15 this.handler = handler;
16 }
17
18 // 实现接口方法 simulate
19 public short simulate(int arg1, long arg2, String arg3)
20 throws ExceptionA, ExceptionB {
21
22 // 第一步是获取 simulate 方法的 Method 对象
23 java.lang.reflect.Method method = null;
24 try{
25 method = Simulator.class.getMethod(
26 "simulate",
27 new Class[] {int.class, long.class, String.class} );
28 } catch(Exception e) {
29 // 异常处理 1(略)
30 }
31
32 // 第二步是调用 handler 的 invoke 方法分派转发方法调用
33 Object r = null;
34 try {
35 r = handler.invoke(this,
36 method,
37 // 对于原始类型参数需要进行装箱操作
38 new Object[] {new Integer(arg1), new Long(arg2), arg3});
39 }catch(Throwable e) {
40 // 异常处理 2(略)
41 }
42 // 第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
43 return ((Short)r).shortValue();
44 }
45 }

模拟推演为了突出通用逻辑所以更多地关注正常流程,而淡化了错误处理,但在实际中错误处理同样非常重要。从以上的推演中我们可以得出一个非常通用的结构化流程:第一步从代理接口获取被调用的方法对象,第二步分派方法到调用处理器执行,第三步返回结果。在这之中,所有的信息都是可以已知的,比如接口名、方法名、参数类型、返回类型以及所需的装箱和拆箱操作,那么既然我们手工编写是如此,那又有什么理由不相信 ProxyGenerator 不会做类似的实现呢?至少这是一种比较可能的实现。

接下来让我们把注意力重新回到先前被淡化的错误处理上来。在异常处理 1 处,由于我们有理由确保所有的信息如接口名、方法名和参数类型都准确无误,所以这部分异常发生的概率基本为零,所以基本可以忽略。而异常处理 2 处,我们需要思考得更多一些。回想一下,接口方法可能声明支持一个异常列表,而调用处理器 invoke 方法又可能抛出与接口方法不支持的异常,再回想一下先前提及的 Java 动态代理的关于异常处理的特点,对于不支持的异常,必须抛 UndeclaredThrowableException 运行时异常。所以通过再次推演,我们可以得出一个更加清晰的异常处理 2 的情况:

  1 //细化异常处理
2 Object r = null;
3
4 try {
5 r = handler.invoke(this,
6 method,
7 new Object[] {new Integer(arg1), new Long(arg2), arg3});
8
9 } catch( ExceptionA e) {
10
11 // 接口方法支持 ExceptionA,可以抛出
12 throw e;
13
14 } catch( ExceptionB e ) {
15 // 接口方法支持 ExceptionB,可以抛出
16 throw e;
17
18 } catch(Throwable e) {
19 // 其他不支持的异常,一律抛 UndeclaredThrowableException
20 throw new UndeclaredThrowableException(e);
21 }

这样我们就完成了对动态代理类的推演实现。推演实现遵循了一个相对固定的模式,可以适用于任意定义的任何接口,而且代码生成所需的信息都是可知的,那么有理由相信即使是机器自动编写的代码也有可能延续这样的风格,至少可以保证这是可行的。

下面结合实际的应用代码看一下JDK中动态代理的实现原理:

  1 package dynamic.proxy;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Proxy;
6
7 /**
8 * 实现自己的InvocationHandler
9 * @author zyb
10 * @since 2012-8-9
11 *
12 */
13 public class MyInvocationHandler implements InvocationHandler {
14
15 // 目标对象
16 private Object target;
17
18 /**
19 * 构造方法
20 * @param target 目标对象
21 */
22 public MyInvocationHandler(Object target) {
23 super();
24 this.target = target;
25 }
26
27
28 /**
29 * 执行目标对象的方法
30 */
31 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
32
33 // 在目标对象的方法执行之前简单的打印一下
34 System.out.println("------------------before------------------");
35
36 // 执行目标对象的方法
37 Object result = method.invoke(target, args);
38
39 // 在目标对象的方法执行之后简单的打印一下
40 System.out.println("-------------------after------------------");
41
42 return result;
43 }
44
45 /**
46 * 获取目标对象的代理对象
47 * @return 代理对象
48 */
49 public Object getProxy() {
50 return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
51 target.getClass().getInterfaces(), this);
52 }
53 }
54
55 package dynamic.proxy;
56
57 /**
58 * 目标对象实现的接口,用JDK来生成代理对象一定要实现一个接口
59 * @author zyb
60 * @since 2012-8-9
61 *
62 */
63 public interface UserService {
64
65 /**
66 * 目标方法
67 */
68 public abstract void add();
69
70 }
71
72 package dynamic.proxy;
73
74 /**
75 * 目标对象
76 * @author zyb
77 * @since 2012-8-9
78 *
79 */
80 public class UserServiceImpl implements UserService {
81
82 /* (non-Javadoc)
83 * @see dynamic.proxy.UserService#add()
84 */
85 public void add() {
86 System.out.println("--------------------add---------------");
87 }
88 }
89
90 package dynamic.proxy;
91
92 import org.junit.Test;
93
94 /**
95 * 动态代理测试类
96 * @author zyb
97 * @since 2012-8-9
98 *
99 */
100 public class ProxyTest {
101
102 @Test
103 public void testProxy() throws Throwable {
104 // 实例化目标对象
105 UserService userService = new UserServiceImpl();
106
107 // 实例化InvocationHandler
108 MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
109
110 // 根据目标对象生成代理对象
111 UserService proxy = (UserService) invocationHandler.getProxy();
112
113 // 调用代理对象的方法
114 proxy.add();
115
116 }
117 }

执行结果如下:
------------------before------------------
--------------------add---------------
-------------------after------------------

用起来是很简单吧,其实这里基本上就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了增强。Spring的AOP实现其实就是用了Proxy和InvocationHandler这两个东西的。(PS:理解AOP思想)

具体的内部实现其实也并非特别难以理解,下面看一下JDK是怎样生成代理对象的。既然生成代理对象是用的Proxy类的静态方newProxyInstance和,那么我们就去它的源码里看一下它到底都做了些什么:

  1 /**
2 * loader:类加载器
3 * interfaces:目标对象实现的接口
4 * h:InvocationHandler的实现类
5 */
6 public static Object newProxyInstance(ClassLoader loader,
7 Class<?>[] interfaces,
8 InvocationHandler h)
9 throws IllegalArgumentException
10 {
11 if (h == null) {
12 throw new NullPointerException();
13 }
14
15 /*
16 * Look up or generate the designated proxy class.
17 */
18 Class cl = getProxyClass(loader, interfaces);
19
20 /*
21 * Invoke its constructor with the designated invocation handler.
22 */
23 try {
24 // 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
25 Constructor cons = cl.getConstructor(constructorParams);
26 // 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法
27 return (Object) cons.newInstance(new Object[] { h });
28 } catch (NoSuchMethodException e) {
29 throw new InternalError(e.toString());
30 } catch (IllegalAccessException e) {
31 throw new InternalError(e.toString());
32 } catch (InstantiationException e) {
33 throw new InternalError(e.toString());
34 } catch (InvocationTargetException e) {
35 throw new InternalError(e.toString());
36 }
37 }
38
  1 public static Class<?> getProxyClass(ClassLoader loader,
2 Class<?>... interfaces)
3 throws IllegalArgumentException
4 {
5 // 如果目标类实现的接口数大于65535个则抛出异常(我XX,谁会写这么NB的代码啊?)
6 if (interfaces.length > 65535) {
7 throw new IllegalArgumentException("interface limit exceeded");
8 }
9
10 // 声明代理对象所代表的Class对象(有点拗口)
11 Class proxyClass = null;
12
13 String[] interfaceNames = new String[interfaces.length];
14
15 Set interfaceSet = new HashSet(); // for detecting duplicates
16
17 // 遍历目标类所实现的接口
18 for (int i = 0; i < interfaces.length; i++) {
19
20 // 拿到目标类实现的接口的名称
21 String interfaceName = interfaces[i].getName();
22 Class interfaceClass = null;
23 try {
24 // 加载目标类实现的接口到内存中
25 interfaceClass = Class.forName(interfaceName, false, loader);
26 } catch (ClassNotFoundException e) {
27 }
28 if (interfaceClass != interfaces[i]) {
29 throw new IllegalArgumentException(
30 interfaces[i] + " is not visible from class loader");
31 }
32
33 // 中间省略了一些无关紧要的代码 .......
34
35 // 把目标类实现的接口代表的Class对象放到Set中
36 interfaceSet.add(interfaceClass);
37
38 interfaceNames[i] = interfaceName;
39 }
40
41 // 把目标类实现的接口名称作为缓存(Map)中的key
42 Object key = Arrays.asList(interfaceNames);
43
44 Map cache;
45
46 synchronized (loaderToCache) {
47 // 从缓存中获取cache
48 cache = (Map) loaderToCache.get(loader);
49 if (cache == null) {
50 // 如果获取不到,则新建地个HashMap实例
51 cache = new HashMap();
52 // 把HashMap实例和当前加载器放到缓存中
53 loaderToCache.put(loader, cache);
54 }
55
56 }
57
58 synchronized (cache) {
59
60 do {
61 // 根据接口的名称从缓存中获取对象
62 Object value = cache.get(key);
63 if (value instanceof Reference) {
64 proxyClass = (Class) ((Reference) value).get();
65 }
66 if (proxyClass != null) {
67 // 如果代理对象的Class实例已经存在,则直接返回
68 return proxyClass;
69 } else if (value == pendingGenerationMarker) {
70 try {
71 cache.wait();
72 } catch (InterruptedException e) {
73 }
74 continue;
75 } else {
76 cache.put(key, pendingGenerationMarker);
77 break;
78 }
79 } while (true);
80 }
81
82 try {
83 // 中间省略了一些代码 .......
84
85 // 这里就是动态生成代理对象的最关键的地方
86 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
87 proxyName, interfaces);
88 try {
89 // 根据代理类的字节码生成代理类的实例
90 proxyClass = defineClass0(loader, proxyName,
91 proxyClassFile, 0, proxyClassFile.length);
92 } catch (ClassFormatError e) {
93 throw new IllegalArgumentException(e.toString());
94 }
95 }
96 // add to set of all generated proxy classes, for isProxyClass
97 proxyClasses.put(proxyClass, null);
98
99 }
100 // 中间省略了一些代码 .......
101
102 return proxyClass;
103 }
104

进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方。

  1  public static byte[] generateProxyClass(final String name,
2 Class[] interfaces)
3 {
4 ProxyGenerator gen = new ProxyGenerator(name, interfaces);
5 // 这里动态生成代理类的字节码,由于比较复杂就不进去看了
6 final byte[] classFile = gen.generateClassFile();
7
8 // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上
9 if (saveGeneratedFiles) {
10 java.security.AccessController.doPrivileged(
11 new java.security.PrivilegedAction<Void>() {
12 public Void run() {
13 try {
14 FileOutputStream file =
15 new FileOutputStream(dotToSlash(name) + ".class");
16 file.write(classFile);
17 file.close();
18 return null;
19 } catch (IOException e) {
20 throw new InternalError(
21 "I/O exception saving generated file: " + e);
22 }
23 }
24 });
25 }
26
27 // 返回代理类的字节码
28 return classFile;
29 }
30

现在,JDK是怎样动态生成代理类的字节的原理已经一目了然了。
好了,再来解决另外一个问题,那就是由谁来调用InvocationHandler的invoke方法的。要解决这个问题就要看一下JDK到底为我们生成了一个什么东西。用以下代码可以获取到JDK为我们生成的字节码并写到硬盘中。

  1 package dynamic.proxy;
2
3 import java.io.FileOutputStream;
4 import java.io.IOException;
5
6 import sun.misc.ProxyGenerator;
7
8 /**
9 * 代理类的生成工具
10 * @author zyb
11 * @since 2012-8-9
12 */
13 public class ProxyGeneratorUtils {
14
15 /**
16 * 把代理类的字节码写到硬盘上
17 * @param path 保存路径
18 */
19 public static void writeProxyClassToHardDisk(String path) {
20 // 第一种方法,这种方式在刚才分析ProxyGenerator时已经知道了
21 // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
22
23 // 第二种方法
24
25 // 获取代理类的字节码
26 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());
27
28 FileOutputStream out = null;
29
30 try {
31 out = new FileOutputStream(path);
32 out.write(classFile);
33 out.flush();
34 } catch (Exception e) {
35 e.printStackTrace();
36 } finally {
37 try {
38 out.close();
39 } catch (IOException e) {
40 e.printStackTrace();
41 }
42 }
43 }
44 }
45
46 package dynamic.proxy;
47
48 import org.junit.Test;
49
50 /**
51 * 动态代理测试类
52 * @author zyb
53 * @since 2012-8-9
54 *
55 */
56 public class ProxyTest {
57
58 @Test
59 public void testProxy() throws Throwable {
60 // 实例化目标对象
61 UserService userService = new UserServiceImpl();
62
63 // 实例化InvocationHandler
64 MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
65
66 // 根据目标对象生成代理对象
67 UserService proxy = (UserService) invocationHandler.getProxy();
68
69 // 调用代理对象的方法
70 proxy.add();
71
72 }
73
74 @Test
75 public void testGenerateProxyClass() {
76 ProxyGeneratorUtils.writeProxyClassToHardDisk("F:/$Proxy11.class");
77 }
78 }
79

通过以上代码,就可以在F盘上生成一个$Proxy.class文件了,现在用反编译工具来看一下这个class文件里面的内容。

  1 // Decompiled by DJ v3.11.11.95 Copyright 2009 Atanas Neshkov  Date: 2012/8/9 20:11:32
2 // Home Page: http://members.fortunecity.com/neshkov/dj.html http://www.neshkov.com/dj.html - Check often for new version!
3 // Decompiler options: packimports(3)
4
5 import dynamic.proxy.UserService;
6 import java.lang.reflect.*;
7
8 public final class $Proxy11 extends Proxy
9 implements UserService
10 {
11
12 // 构造方法,参数就是刚才传过来的MyInvocationHandler类的实例
13 public $Proxy11(InvocationHandler invocationhandler)
14 {
15 super(invocationhandler);
16 }
17
18 public final boolean equals(Object obj)
19 {
20 try
21 {
22 return ((Boolean)super.h.invoke(this, m1, new Object[] {
23 obj
24 })).booleanValue();
25 }
26 catch(Error _ex) { }
27 catch(Throwable throwable)
28 {
29 throw new UndeclaredThrowableException(throwable);
30 }
31 }
32
33 /**
34 * 这个方法是关键部分
35 */
36 public final void add()
37 {
38 try
39 {
40 // 实际上就是调用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了
41 super.h.invoke(this, m3, null);
42 return;
43 }
44 catch(Error _ex) { }
45 catch(Throwable throwable)
46 {
47 throw new UndeclaredThrowableException(throwable);
48 }
49 }
50
51 public final int hashCode()
52 {
53 try
54 {
55 return ((Integer)super.h.invoke(this, m0, null)).intValue();
56 }
57 catch(Error _ex) { }
58 catch(Throwable throwable)
59 {
60 throw new UndeclaredThrowableException(throwable);
61 }
62 }
63
64 public final String toString()
65 {
66 try
67 {
68 return (String)super.h.invoke(this, m2, null);
69 }
70 catch(Error _ex) { }
71 catch(Throwable throwable)
72 {
73 throw new UndeclaredThrowableException(throwable);
74 }
75 }
76
77 private static Method m1;
78 private static Method m3;
79 private static Method m0;
80 private static Method m2;
81
82 // 在静态代码块中获取了4个方法:Object中的equals方法、UserService中的add方法、Object中的hashCode方法、Object中toString方法
83 static
84 {
85 try
86 {
87 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
88 Class.forName("java.lang.Object")
89 });
90 m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
91 m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
92 m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
93 }
94 catch(NoSuchMethodException nosuchmethodexception)
95 {
96 throw new NoSuchMethodError(nosuchmethodexception.getMessage());
97 }
98 catch(ClassNotFoundException classnotfoundexception)
99 {
100 throw new NoClassDefFoundError(classnotfoundexception.getMessage());
101 }
102 }
103 }
104

如果还是不理解那我们再做一个完整的小例子:

1.要代理的接口:

  1 package note.com;
2
3 /**
4 * Girl接口
5 * @author lxz
6 *
7 */
8 public interface IGirl {
9 void sayHello();
10 }

2.接口的实现类,实质也是需要利用动态代理扩展功能的类。

  1 package note.com;
2
3 /**
4 * 具体Girl
5 * @author lxz
6 *
7 */
8 public class MyGirl implements IGirl {
9 public void sayHello() {
10 System.out.println("如花似玉石榴姐");
11 }
12 }

3.代理类,这个类需要实现InvocationHandler接口来实现接口中invoke()方法来明确增强功能的具体实现,由JDK负责调用,这里的bind方法就是获取代理实例对象的方法。

  1 package note.com;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Proxy;
6
7 /**
8 * 代理类
9 * 功能:给IGirl实现类增加介绍
10 * @author lxz
11 *
12 */
13 public class ProxyGirl implements InvocationHandler {
14 Object originalObj;
15
16 Object bind(Object originalObj) {
17 this.originalObj = originalObj;
18 return Proxy.newProxyInstance(originalObj.getClass()
19 .getClassLoader(), originalObj.getClass().getInterfaces(),
20 this);
21 }
22
23 public Object invoke(Object proxy, Method method, Object[] args)
24 throws Throwable {
25 System.out.println("第一美女是:");
26 return method.invoke(originalObj, args);
27 }
28 }

4.下面写个测试类,其实就是将需要代理的接口的实现类的实例对象传递给代理类的一个方法通过代理类的这个方法(这里是方法bind())获取代理实例,再通过这个对象调用已被增强的方法:

  1 package note.com;
2
3 /**
4 * 测试类
5 *
6 * @author lxz
7 *
8 */
9 public class Test {
10
11 public static void main(String[] args) {
12 IGirl hello = (IGirl) new ProxyGirl().bind(new MyGirl());
13 hello.sayHello();
14      System.out.println(hello.getClass().getName());
15 }
16
17 }

结果:

第一美女是:
如花似玉石榴姐
com.sun.proxy.$Proxy0

这里我们再次查看一下com.sun.proxy.$Proxy0这个类的代码:(PS:其实和上边的例子是一样的)

  1 package note.com;
2
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
8 /**
9 * 动态生成的类字节码的反编译结果
10 *
11 */
12 public final class $Proxy0 extends Proxy implements IGirl {
13
14 private static Method m3;
15 private static Method m1;
16 private static Method m0;
17 private static Method m2;
18
19 /*
20 * 构造函数传入能够访问真实对象的代理类,这个实际是上例Test中的new ProxyGirl()
21 */
22 protected $Proxy0(InvocationHandler h) {
23 super(h);
24 }
25
26 /*
27 * 代理实现sayHello,
28 */
29 public void sayHello() {
30 try {
31 this.h.invoke(this, m3, null);
32 } catch (RuntimeException localRuntimeException) {
33 throw localRuntimeException;
34 } catch (Throwable localThrowable) {
35 throw new UndeclaredThrowableException(localThrowable);
36 }
37
38 }
39
40 /*
41 * 代理实现继承自Object的equals
42 */
43 public void equals() {
44 try {
45 this.h.invoke(this, m1, null);
46 } catch (RuntimeException localRuntimeException) {
47 throw localRuntimeException;
48 } catch (Throwable localThrowable) {
49 throw new UndeclaredThrowableException(localThrowable);
50 }
51 }
52
53 /*
54 * 代理实现继承自Object的hashCode
55 */
56 public int hashCode() {
57 try {
58 return (Integer) this.h.invoke(this, m0, null);
59 } catch (RuntimeException localRuntimeException) {
60 throw localRuntimeException;
61 } catch (Throwable localThrowable) {
62 throw new UndeclaredThrowableException(localThrowable);
63 }
64 }
65
66 /*
67 * 代理实现继承自Object的toString
68 */
69 public String toString() {
70 try {
71 return (String) this.h.invoke(this, m2, null);
72 } catch (RuntimeException localRuntimeException) {
73 throw localRuntimeException;
74 } catch (Throwable localThrowable) {
75 throw new UndeclaredThrowableException(localThrowable);
76 }
77 }
78
79 /*
80 * 初始化真实对象中的所有方法
81 */
82 static {
83 try {
84 m3 = Class.forName("note.com.IGirl").getMethod("sayHello",
85 new Class[0]);
86 m1 = Class.forName("java.lang.Object").getMethod("equals",
87 new Class[] { Class.forName("java.lang.Object") });
88 m0 = Class.forName("java.lang.Object").getMethod("equals",
89 new Class[0]);
90 m2 = Class.forName("java.lang.Object").getMethod("equals",
91 new Class[0]);
92 } catch (NoSuchMethodException localNoSuchMethodException) {
93 throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
94 } catch (ClassNotFoundException localClassNotFoundException) {
95 throw new NoClassDefFoundError(
96 localClassNotFoundException.getMessage());
97 }
98 }
99
100 }

好了,到目前为止,前面 的两个问题都已经知道回事了,现在再用JDK动态代理的时候就不只会用而已了,真正的达到了“知其然,知其所以然”的目的。

其中注意的几个容易懵逼小问题:

  • 动态代理中用到了匿名内部类,所以生产的类必然是final修饰的。其实内部类的生命周期的特点,匿名内部类把需要访问的外部变量作为一个隐藏的字段,这样得到了一个变量的引用拷贝 ,使用final修饰符不仅会保持对象不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期。(PS:为什么匿名内部类参数必须为final类型;加载中...);
  • Spring的AOP机制就是利用动态代理重新生成类,并在原有的基础上灵活的动态添加一些功能,例如:日志打印,拦截信息等等;
  • 通过观察反编译后的动态类,这个逻辑并不复杂,主要功能是对所有的方法进行初始化,到执行某个方法的时候调用我们自己实现的代理类去执行扩展功能和原始类的方法.(PS:this.h.invoke)
  • $Proxy0访问真实的类对象通过InvocationHandler的实现类调用.Proxy.newProxyInstance这个方法做了很多事情,动态代理扩展功能并没有在$Proxy0中加入,而是回调InvocationHandler的接口,通过子类实现Invoke方法扩展.
  • 从调用关系上看使用动态代理前后:
  • 左边:是原始的调用关系,原始类中有什么逻辑就执行什么.

    右边:是动态代理以后,通过动态代理生成类的对象调用代理类,代理类调用扩展逻辑,然后调用原始类对象的逻辑.由此实现了对原始类的动态扩展.

由此看来封装一个代理服务类,当需要调用方法的前后做一些事情的时候,利用代理服务类来生成对象就可以了,这就是动态代理的应用场景。

美中不足:

诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。

但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例。

引用:IBMRejoy;为什么匿名内部类参数必须为final类型;动态代理的类的特点;加载中...

JAVA动态代理的全面深层理解的更多相关文章

  1. 深入理解 Java 动态代理机制

    Java 有两种代理方式,一种是静态代理,另一种是动态代理.对于静态代理,其实就是通过依赖注入,对对象进行封装,不让外部知道实现的细节.很多 API 就是通过这种形式来封装的. 代理模式结构图(图片来 ...

  2. 理解java动态代理

    java动态代理是java语言的一项高级特性.在平时的项目开发中,可能很难遇到动态代理的案例.但是动态代理在很多框架中起着不可替代的作用,例如Spring的AOP.今天我们就聊一聊java动态代理的实 ...

  3. JAVA动态代理模式(从现实生活角度理解代码原理)

    所谓动态代理,即通过代理类:Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联. java动态代理主要是使用java.lang.reflect包中的两个 ...

  4. 彻底理解JAVA动态代理

    代理设计模式 定义:为其他对象提供一种代理以控制对这个对象的访问. 代理模式的结构如下图所示. 动态代理使用 java动态代理机制以巧妙的方式实现了代理模式的设计理念. 代理模式示例代码 public ...

  5. Java 动态代理机制详解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  6. Java 动态代理

    被代理的接口特点: 1. 不能有重复的接口,以避免动态代理类代码生成时的编译错误. 2. 这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败. 3. 需被代理的所有非 pub ...

  7. java高级---->Java动态代理的原理

    Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程 ...

  8. Java 动态代理机制分析及扩展

    Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...

  9. [转]Java 动态代理机制分析及扩展

    引言 Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执 ...

随机推荐

  1. 使用openresty + lua 搭建api 网关(一)安装openresty ,并添加lua模块

    openresty 有点不多说,网上各种介绍,先安装吧. 官方操作在此,http://openresty.org/cn/installation.html, tar -xzvf openresty-V ...

  2. 剑指offer 面试10题

    面试10题: 题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项.n<=39 n=0时,f(n)=0 n=1时,f(n)=1 n>1时,f(n)=f(n-1 ...

  3. NodeJS 加入windows7服务 开机运行 nssm

    from:http://blog.sina.com.cn/s/blog_5ef5f2630101aql8.html 首先需要到http://nssm.cc/download/?page=downloa ...

  4. 03 Spring框架 bean的属性以及bean前处理和bean后处理

    整理了一下之前学习spring框架时候的一点笔记.如有错误欢迎指正,不喜勿喷. 上一节我们给出了三个小demo,具体的流程是这样的: 1.首先在aplicationContext.xml中添加< ...

  5. 爬虫学习笔记(2)--创建scrapy项目&&css选择器

    一.手动创建scrapy项目---------------- 安装scrapy: pip install -i https://pypi.douban.com/simple/  scrapy    1 ...

  6. PAT 天梯赛 L1-026. I Love GPLT 【水】

    题目链接 https://www.patest.cn/contests/gplt/L1-026 AC代码 #include <iostream> #include <cstdio&g ...

  7. 前端之HTML基础

    一.初识HTML 1.web服务的本质 方式一:服务端 import socket def main(): sock = socket.socket(socket.AF_INET, socket.SO ...

  8. 灰色3D按钮组合

    在线演示 本地下载

  9. 20145231《Java程序设计》第四次实验报告

    实验四 Android开发基础 实验内容 •安装Android Studio •运行安卓AVD模拟器 •使用Android运行出模拟手机并显示自己的学号 实验步骤 一.安装Android Studio ...

  10. Go panic recover

    panic 1. 停止当前函数执行 2. 一直向上返回,执行每一层的defer 3. 如果没有遇到recover, 程序退出 recover 1. 仅在defer调用中使用 2. 获取panic的值 ...