Java 动态代理

    Java动态代理主要使用场景:

    1. 统计方法执行所耗时间。
    2. 在方法执行前后添加日志。
    3. 检测方法的参数或返回值。
    4. 方法访问权限控制。
    5. 方法Mock测试。

    创建动态代理类会使用到java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。java.lang.reflect.Proxy主要用于生成动态代理类Class、创建代理类实例,该类实现了java.io.Serializable接口。

    java.lang.reflect.Proxy类主要方法如下:

    java.lang.reflect.InvocationHandler接口用于调用Proxy类生成的代理类方法,该类只有一个invoke方法。

    java.lang.reflect.InvocationHandler接口代码(注释直接搬的JDK6中文版文档):

    1. package java.lang.reflect;
    2. import java.lang.reflect.Method;
    3. /**
    4. * 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并
    5. * 将其指派到它的调用处理程序的 invoke 方法。
    6. */
    7. public interface InvocationHandler {
    8. /**
    9. * 在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
    10. *
    11. * @param proxy 在其上调用方法的代理实例
    12. * @param method 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明
    13. * 方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
    14. * @param args 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,
    15. * 则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer
    16. * 或 java.lang.Boolean)的实例中。
    17. * @return 从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,
    18. * 则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。
    19. * 如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出
    20. * NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,
    21. * 则代理实例上的方法调用将抛出 ClassCastException。
    22. * @throws Throwable 从代理实例上的方法调用抛出的异常。该异常的类型必须可以分配到在接口方法的
    23. * throws 子句中声明的任一异常类型或未经检查的异常类型 java.lang.RuntimeException 或
    24. * java.lang.Error。如果此方法抛出经过检查的异常,该异常不可分配到在接口方法的 throws 子句中
    25. * 声明的任一异常类型,代理实例的方法调用将抛出包含此方法曾抛出的异常的
    26. */
    27. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    28. }

    使用java.lang.reflect.Proxy动态创建类对象

    前面章节我们讲到了ClassLoaderUnsafe都有一个叫做defineClassXXXnative方法,我们可以通过调用这个native方法动态的向JVM创建一个类对象,而java.lang.reflect.Proxy类恰好也有这么一个native方法,所以我们也将可以通过调用java.lang.reflect.ProxydefineClass0方法实现动态创建类对象。

    ProxyDefineClassTest示例代码:

    1. package com.anbai.sec.proxy;
    2. import java.lang.reflect.Method;
    3. import java.lang.reflect.Proxy;
    4. import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_BYTES;
    5. import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_NAME;
    6. /**
    7. * Creator: yz
    8. * Date: 2020/1/15
    9. */
    10. public class ProxyDefineClassTest {
    11. public static void main(String[] args) {
    12. // 获取系统的类加载器,可以根据具体情况换成一个存在的类加载器
    13. ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    14. try {
    15. // 反射java.lang.reflect.Proxy类获取其中的defineClass0方法
    16. Method method = Proxy.class.getDeclaredMethod("defineClass0", new Class[]{
    17. ClassLoader.class, String.class, byte[].class, int.class, int.class
    18. });
    19. // 修改方法的访问权限
    20. method.setAccessible(true);
    21. // 反射调用java.lang.reflect.Proxy.defineClass0()方法,动态向JVM注册
    22. // com.anbai.sec.classloader.TestHelloWorld类对象
    23. Class helloWorldClass = (Class) method.invoke(null, new Object[]{
    24. classLoader, TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length
    25. });
    26. // 输出TestHelloWorld类对象
    27. System.out.println(helloWorldClass);
    28. } catch (Exception e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }

    程序运行结果:

    1. class com.anbai.sec.classloader.TestHelloWorld

    Proxy.newProxyInstance示例代码:

    Proxy.getProxyClass反射示例代码:

    1. // 创建UnixFileSystem类实例
    2. FileSystem fileSystem = new UnixFileSystem();
    3. // 创建动态代理处理类
    4. InvocationHandler handler = new JDKInvocationHandler(fileSystem);
    5. // 通过指定类加载器、类实现的接口数组生成一个动态代理类
    6. Class proxyClass = Proxy.getProxyClass(
    7. FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
    8. );
    9. // 使用反射获取Proxy类构造器并创建动态代理类实例
    10. FileSystem proxyInstance = (FileSystem) proxyClass.getConstructor(
    11. new Class[]{InvocationHandler.class}).newInstance(new Object[]{handler}

    动态代理添加方法调用日志示例

    假设我们有一个叫做FileSystem接口,UnixFileSystem类实现了FileSystem接口,我们可以使用JDK动态代理的方式给FileSystem的接口方法执行前后都添加日志输出。

    com.anbai.sec.proxy.FileSystem示例代码:

    1. package com.anbai.sec.proxy;
    2. import java.io.File;
    3. import java.io.Serializable;
    4. /**
    5. * Creator: yz
    6. * Date: 2020/1/14
    7. */
    8. public interface FileSystem extends Serializable {
    9. String[] list(File file);
    10. }

    com.anbai.sec.proxy.UnixFileSystem示例代码:

    1. package com.anbai.sec.proxy;
    2. import java.io.File;
    3. /**
    4. * Creator: yz
    5. * Date: 2020/1/14
    6. */
    7. public class UnixFileSystem implements FileSystem {
    8. /* -- Disk usage -- */
    9. public int spaceTotal = 996;
    10. @Override
    11. public String[] list(File file) {
    12. System.out.println("正在执行[" + this.getClass().getName() + "]类的list方法,参数:[" + file + "]");
    13. return file.list();
    14. }
    15. }

    com.anbai.sec.proxy.JDKInvocationHandler示例代码:

    com.anbai.sec.proxy.FileSystemProxyTest示例代码:

    1. package com.anbai.sec.proxy;
    2. import java.io.File;
    3. import java.lang.reflect.Proxy;
    4. import java.util.Arrays;
    5. /**
    6. * Creator: yz
    7. * Date: 2020/1/14
    8. */
    9. public class FileSystemProxyTest {
    10. public static void main(String[] args) {
    11. // 创建UnixFileSystem类实例
    12. FileSystem fileSystem = new UnixFileSystem();
    13. // 使用JDK动态代理生成FileSystem动态代理类实例
    14. FileSystem proxyInstance = (FileSystem) Proxy.newProxyInstance(
    15. FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
    16. new Class[]{FileSystem.class}, // 定义动态代理生成的类实现的接口
    17. new JDKInvocationHandler(fileSystem)// 动态代理处理类
    18. );
    19. System.out.println("动态代理生成的类名:" + proxyInstance.getClass());
    20. System.out.println("----------------------------------------------------------------------------------------");
    21. System.out.println("动态代理生成的类名toString:" + proxyInstance.toString());
    22. System.out.println("----------------------------------------------------------------------------------------");
    23. // 使用动态代理的方式UnixFileSystem方法
    24. String[] files = proxyInstance.list(new File("."));
    25. System.out.println("----------------------------------------------------------------------------------------");
    26. System.out.println("UnixFileSystem.list方法执行结果:" + Arrays.toString(files));
    27. System.out.println("----------------------------------------------------------------------------------------");
    28. boolean isFileSystem = proxyInstance instanceof FileSystem;
    29. boolean isUnixFileSystem = proxyInstance instanceof UnixFileSystem;
    30. System.out.println("动态代理类[" + proxyInstance.getClass() + "]是否是FileSystem类的实例:" + isFileSystem);
    31. System.out.println("----------------------------------------------------------------------------------------");
    32. System.out.println("动态代理类[" + proxyInstance.getClass() + "]是否是UnixFileSystem类的实例:" + isUnixFileSystem);
    33. System.out.println("----------------------------------------------------------------------------------------");
    34. }
    35. }

    程序执行结果:

    1. 动态代理生成的类名:class com.sun.proxy.$Proxy0
    2. ----------------------------------------------------------------------------------------
    3. 动态代理生成的类名toString:com.anbai.sec.proxy.UnixFileSystem@194d6112
    4. ----------------------------------------------------------------------------------------
    5. 即将调用[com.anbai.sec.proxy.UnixFileSystem]类的[list]方法...
    6. 正在执行[com.anbai.sec.proxy.UnixFileSystem]类的list方法,参数:[.]
    7. 已完成[com.anbai.sec.proxy.UnixFileSystem]类的[list]方法调用...
    8. ----------------------------------------------------------------------------------------
    9. ----------------------------------------------------------------------------------------
    10. 动态代理类[class com.sun.proxy.$Proxy0]是否是FileSystem类的实例:true
    11. ----------------------------------------------------------------------------------------
    12. 动态代理类[class com.sun.proxy.$Proxy0]是否是UnixFileSystem类的实例:false
    13. ----------------------------------------------------------------------------------------

    动态代理生成出来的类有如下技术细节和特性:

    1. 动态代理的必须是接口类,通过动态生成一个接口实现类来代理接口的方法调用(反射机制)。
    2. 动态代理类会由java.lang.reflect.Proxy.ProxyClassFactory创建。
    3. ProxyClassFactory会调用sun.misc.ProxyGenerator类生成该类的字节码,并调用java.lang.reflect.Proxy.defineClass0()方法将该类注册到JVM
    4. 该类继承于java.lang.reflect.Proxy并实现了需要被代理的接口类,因为java.lang.reflect.Proxy类实现了java.io.Serializable接口,所以被代理的类支持序列化/反序列化
    5. 该类实现了代理接口类(示例中的接口类是com.anbai.sec.proxy.FileSystem),会通过ProxyGenerator动态生成接口类(FileSystem)的所有方法,
    6. 该类因为实现了代理的接口类,所以当前类是代理的接口类的实例(为true),但不是代理接口类的实现类的实例(proxyInstance instanceof UnixFileSystemfalse)。
    7. 该类方法中包含了被代理的接口类的所有方法,通过调用动态代理处理类(InvocationHandler)的invoke方法获取方法执行结果。
    8. 该类代理的方式重写了java.lang.Object类的toStringhashCodeequals方法。
    9. 如果动过动态代理生成了多个动态代理类,新生成的类名中的0会自增,如com.sun.proxy.$Proxy0/$Proxy1/$Proxy2

    动态代理生成的com.sun.proxy.$Proxy0类代码:

    1. package com.sun.proxy.$Proxy0;
    2. import java.io.File;
    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 FileSystem {
    8. private static Method m1;
    9. // 实现的FileSystem接口方法,如果FileSystem里面有多个方法那么在这个类中将从m3开始n个成员变量
    10. private static Method m3;
    11. private static Method m0;
    12. private static Method m2;
    13. public $Proxy0(InvocationHandler var1) {
    14. super(var1);
    15. }
    16. public final boolean equals(Object var1) {
    17. try {
    18. return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
    19. } catch (RuntimeException | Error var3) {
    20. throw var3;
    21. } catch (Throwable var4) {
    22. throw new UndeclaredThrowableException(var4);
    23. }
    24. }
    25. public final String[] list(File var1) {
    26. try {
    27. return (String[]) super.h.invoke(this, m3, new Object[]{var1});
    28. } catch (RuntimeException | Error var3) {
    29. throw var3;
    30. } catch (Throwable var4) {
    31. throw new UndeclaredThrowableException(var4);
    32. }
    33. }
    34. public final int hashCode() {
    35. try {
    36. return (Integer) super.h.invoke(this, m0, (Object[]) null);
    37. } catch (RuntimeException | Error var2) {
    38. throw var2;
    39. } catch (Throwable var3) {
    40. throw new UndeclaredThrowableException(var3);
    41. }
    42. }
    43. public final String toString() {
    44. try {
    45. return (String) super.h.invoke(this, m2, (Object[]) null);
    46. } catch (RuntimeException | Error var2) {
    47. throw var2;
    48. } catch (Throwable var3) {
    49. throw new UndeclaredThrowableException(var3);
    50. }
    51. }
    52. static {
    53. try {
    54. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
    55. m3 = Class.forName("com.anbai.sec.proxy.FileSystem").getMethod("list", Class.forName("java.io.File"));
    56. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    57. m2 = Class.forName("java.lang.Object").getMethod("toString");
    58. } catch (NoSuchMethodException var2) {
    59. throw new NoSuchMethodError(var2.getMessage());
    60. } catch (ClassNotFoundException var3) {
    61. throw new NoClassDefFoundError(var3.getMessage());
    62. }
    63. }
    64. }

    动态代理类实例序列化问题

    动态代理类符合Java对象序列化条件,并且在序列化/反序列化时会被ObjectInputStream/ObjectOutputStream特殊处理。

    FileSystemProxySerializationTest示例代码:

    程序执行结果:

    1. 反序列化类实例类名:class com.sun.proxy.$Proxy0
    2. 反序列化类实例toString:com.anbai.sec.proxy.UnixFileSystem@b07848

    动态代理生成的类在反序列化/反序列化时不会序列化该类的成员变量,并且serialVersionUID0L ,也将是说将该类的Class对象传递给java.io.ObjectStreamClass的静态lookup方法时,返回的ObjectStreamClass实例将具有以下特性:

    1. 调用其getSerialVersionUID方法将返回0L
    2. 调用其getFields方法将返回长度为零的数组。

    但其父类(java.lang.reflect.Proxy)在序列化时不受影响,父类中的h变量(InvocationHandler)将会被序列化,这个h存储了动态代理类的处理类实例以及动态代理的接口类的实现类的实例。

    动态代理生成的对象(com.sun.proxy.$ProxyXXX)序列化的时候会使用一个特殊的协议:TC_PROXYCLASSDESC(0x7D),这个常量在java.io.ObjectStreamConstants中定义的。在反序列化时也不会调用java.io.ObjectInputStream类的resolveClass方法而是调用resolveProxyClass方法来转换成类对象的。