# Java 动态代理

Java 反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。

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

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

# 动态代理 API

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

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

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

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

高版本的 jdk 已经去掉了 proxy 类的 definclass

package org.chenluo.servlet;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import static org.chenluo.servlet.TestClassLoader.TEST_CLASS_BYTES;
import static org.chenluo.servlet.TestClassLoader.TEST_CLASS_NAME;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.chenluo.servlet.TestClassLoader.TEST_CLASS_BYTES;
import static org.chenluo.servlet.TestClassLoader.TEST_CLASS_NAME;
public class ProxyDefineClassTest {
    public static void main(String[] args) {
        try {
            // 获取系统类加载器
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            // 假设我们已经有字节码文件在指定路径
            byte[] classBytes = TEST_CLASS_BYTES;
            // 获取 Lookup 对象
            Lookup lookup = MethodHandles.lookup();
            // 通过反射获取 Lookup 类中的 defineClass 方法
            Method defineClassMethod = Lookup.class.getDeclaredMethod("defineClass", byte[].class);
            defineClassMethod.setAccessible(true);
            // 调用 defineClass 方法定义类
            Class<?> helloWorldClass = (Class<?>) defineClassMethod.invoke(lookup, (Object) classBytes);
            // 输出 TestHelloWorld 类对象
            System.out.println(helloWorldClass);
            // 创建实例并调用方法
            Object instance = helloWorldClass.getDeclaredConstructor().newInstance();
            Method sayHelloMethod = helloWorldClass.getMethod("sayHello");
            sayHelloMethod.invoke(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

# 详细讲解

  1. 包和导入声明

    package org.chenluo.servlet;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodHandles.Lookup;
    import java.lang.reflect.Method;
    import static org.chenluo.servlet.TestClassLoader.TEST_CLASS_BYTES;
    import static org.chenluo.servlet.TestClassLoader.TEST_CLASS_NAME;
    • 定义包名为 org.chenluo.servlet
    • 导入了 java.lang.invoke.MethodHandlesjava.lang.invoke.MethodHandles.Lookup ,用于处理方法句柄。
    • 导入了 java.lang.reflect.Method ,用于反射操作。
    • 导入了 TestClassLoader 中的常量 TEST_CLASS_BYTESTEST_CLASS_NAME
  2. 主类和主方法

    public class ProxyDefineClassTest {
        public static void main(String[] args) {
            try {
                // 获取系统类加载器
                ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    • 定义了一个名为 ProxyDefineClassTest 的公共类。
    • main 方法中,获取系统类加载器。
  3. 读取字节码

    // 假设我们已经有字节码文件在指定路径
    byte[] classBytes = TEST_CLASS_BYTES;
    • 假设已经有字节码文件,使用 TEST_CLASS_BYTES 变量获取字节码。
  4. 获取 Lookup 对象

    // 获取 Lookup 对象
    Lookup lookup = MethodHandles.lookup();
    • 使用 MethodHandles.lookup() 方法获取 Lookup 对象,用于后续的反射操作。
  5. 通过反射获取 defineClass 方法

    // 通过反射获取 Lookup 类中的 defineClass 方法
    Method defineClassMethod = Lookup.class.getDeclaredMethod("defineClass", byte[].class);
    defineClassMethod.setAccessible(true);
    • 通过反射获取 Lookup 类中的 defineClass 方法,该方法用于定义一个类。
    • 将该方法的访问权限设置为可访问。
  6. 调用 defineClass 方法定义类

    // 调用 defineClass 方法定义类
    Class<?> helloWorldClass = (Class<?>) defineClassMethod.invoke(lookup, (Object) classBytes);
    • 调用 defineClass 方法,将字节码转换为类对象。
    • 通过 lookup 对象调用 defineClassMethod ,并传入字节码数组 classBytes
  7. 输出定义的类对象

    // 输出 TestHelloWorld 类对象
    System.out.println(helloWorldClass);
    • 输出定义的类对象的信息。
  8. 创建实例并调用方法

    // 创建实例并调用方法
    Object instance = helloWorldClass.getDeclaredConstructor().newInstance();
    Method sayHelloMethod = helloWorldClass.getMethod("sayHello");
    sayHelloMethod.invoke(instance);
    • 使用反射创建定义的类的实例。
    • 获取并调用实例的 sayHello 方法。
  9. 异常处理

    } catch (Exception e) {
        e.printStackTrace();
    }
    • 捕获并处理所有异常,打印异常堆栈信息。

# 运行逻辑

  • 首先,通过系统类加载器加载字节码文件。
  • 然后,通过 MethodHandles.Lookup 获取反射操作对象,并通过反射获取 defineClass 方法。
  • 使用 defineClass 方法将字节码转换为类对象,并输出该类对象的信息。
  • 最后,使用反射创建类的实例,并调用其中的方法,验证类定义是否成功。

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

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

org.chenluo.servlet.FileSystem 示例代码:

public interface FileSystem extends Serializable {
    String[] list(File file);
}

org.chenluo.servlet.JDKInvocationHandler 示例代码:

package com.anbai.sec.proxy;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * Creator: yz
 * Date: 2020/1/14
 */
public class JDKInvocationHandler implements InvocationHandler, Serializable {
    private final Object target;
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }
  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 为了不影响测试 Demo 的输出结果,这里忽略掉 toString 方法
        if ("toString".equals(method.getName())) {
            return method.invoke(target, args);
        }
        System.out.println("即将调用[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法...");
        Object obj = method.invoke(target, args);
        System.out.println("已完成[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法调用...");
        return obj;
    }
}

org.chenluo.servlet.FileSystemProxyTest 示例代码:

package com.anbai.sec.proxy;
import java.io.File;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
 * Creator: yz
 * Date: 2020/1/14
 */
public class FileSystemProxyTest {
    public static void main(String[] args) {
        // 创建 UnixFileSystem 类实例,设计多态
        FileSystem fileSystem = new UnixFileSystem();
        // 使用 JDK 动态代理生成 FileSystem 动态代理类实例
        FileSystem proxyInstance = (FileSystem) Proxy.newProxyInstance(
                FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
                new Class[]{FileSystem.class}, // 定义动态代理生成的类实现的接口
                new JDKInvocationHandler(fileSystem)// 动态代理处理类
        );
        System.out.println("动态代理生成的类名:" + proxyInstance.getClass());
        System.out.println("----------------------------------------------------------------------------------------");
        System.out.println("动态代理生成的类名toString:" + proxyInstance.toString());
        System.out.println("----------------------------------------------------------------------------------------");
        // 使用动态代理的方式 UnixFileSystem 方法
        String[] files = proxyInstance.list(new File("."));
        System.out.println("----------------------------------------------------------------------------------------");
        System.out.println("UnixFileSystem.list方法执行结果:" + Arrays.toString(files));
        System.out.println("----------------------------------------------------------------------------------------");
        boolean isFileSystem     = proxyInstance instanceof FileSystem;
        boolean isUnixFileSystem = proxyInstance instanceof UnixFileSystem;
        System.out.println("动态代理类[" + proxyInstance.getClass() + "]是否是FileSystem类的实例:" + isFileSystem);
        System.out.println("----------------------------------------------------------------------------------------");
        System.out.println("动态代理类[" + proxyInstance.getClass() + "]是否是UnixFileSystem类的实例:" + isUnixFileSystem);
        System.out.println("----------------------------------------------------------------------------------------");
    }
}

执行结果

# 动态代理类生成的 $ProxyXXX 类代码分析

java.lang.reflect.Proxy 类是通过创建一个新的 Java类(类名为com.sun.proxy.$ProxyXXX) 的方式来实现无侵入的类方法代理功能的。

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

  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. 该类因为实现了代理的接口类,所以当前类是代理的接口类的实例 ( proxyInstance instanceof FileSystemtrue ),但不是代理接口类的实现类的实例 ( proxyInstance instanceof UnixFileSystemfalse )。
  7. 该类方法中包含了被代理的接口类的所有方法,通过调用动态代理处理类 ( InvocationHandler ) 的 invoke 方法获取方法执行结果。
  8. 该类代理的方式重写了 java.lang.Object 类的 toStringhashCodeequals 方法。
  9. 如果动过动态代理生成了多个动态代理类,新生成的类名中的 0 会自增,如 com.sun.proxy.$Proxy0/$Proxy1/$Proxy2

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

package com.sun.proxy.$Proxy0;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements FileSystem {
    private static Method m1;
  // 实现的 FileSystem 接口方法,如果 FileSystem 里面有多个方法那么在这个类中将从 m3 开始 n 个成员变量
    private static Method m3;
    private static Method m0;
    private static Method m2;
    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }
    public final boolean equals(Object var1) {
        try {
            return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final String[] list(File var1) {
        try {
            return (String[]) super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final int hashCode() {
        try {
            return (Integer) super.h.invoke(this, m0, (Object[]) null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final String toString() {
        try {
            return (String) super.h.invoke(this, m2, (Object[]) null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.anbai.sec.proxy.FileSystem").getMethod("list", Class.forName("java.io.File"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

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

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

FileSystemProxySerializationTest 示例代码:

package com.anbai.sec.proxy;
import java.io.*;
import java.lang.reflect.Proxy;
/**
 * Creator: yz
 * Date: 2020/1/14
 */
public class FileSystemProxySerializationTest {
   public static void main(String[] args) {
      try {
         // 创建 UnixFileSystem 类实例
         FileSystem fileSystem = new UnixFileSystem();
         // 使用 JDK 动态代理生成 FileSystem 动态代理类实例
         FileSystem proxyInstance = (FileSystem) Proxy.newProxyInstance(
               FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
               new Class[]{FileSystem.class}, // 定义动态代理生成的类实现的接口
               new JDKInvocationHandler(fileSystem)// 动态代理处理类
         );
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         // 创建 Java 对象序列化输出流对象
         ObjectOutputStream out = new ObjectOutputStream(baos);
         // 序列化动态代理类
         out.writeObject(proxyInstance);
         out.flush();
         out.close();
         // 利用动态代理类生成的二进制数组创建二进制输入流对象用于反序列化操作
         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
         // 通过反序列化输入流 (bais), 创建 Java 对象输入流 (ObjectInputStream) 对象
         ObjectInputStream in = new ObjectInputStream(bais);
         // 反序列化输入流数据为 FileSystem 对象
         FileSystem test = (FileSystem) in.readObject();
         System.out.println("反序列化类实例类名:" + test.getClass());
         System.out.println("反序列化类实例toString:" + test.toString());
      } catch (IOException e) {
         e.printStackTrace();
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }
   }
}

创建动态代理对象,对其进行序列化,将序列化后的数据存储到字节数组,然后从字节数组中反序列化出原始对象。

程序执行结果:

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

# 主类和主方法

public class FileSystemProxySerializationTest {
    public static void main(String[] args) {
        try {
            // 创建 UnixFileSystem 类实例
            FileSystem fileSystem = new UnixFileSystem();
  • 定义了 FileSystemProxySerializationTest 主类。
  • main 方法中,创建一个 UnixFileSystem 实例。

# 创建动态代理对象

// 使用 JDK 动态代理生成 FileSystem 动态代理类实例
            FileSystem proxyInstance = (FileSystem) Proxy.newProxyInstance(
                    FileSystem.class.getClassLoader(), // 指定动态代理类的类加载器
                    new Class[]{FileSystem.class}, // 定义动态代理生成的类实现的接口
                    new JDKInvocationHandler(fileSystem) // 动态代理处理类
            );
  • 使用 Proxy.newProxyInstance 创建一个动态代理对象 proxyInstance
  • 指定类加载器、接口和处理器( JDKInvocationHandler )用于创建代理对象。

# 序列化动态代理对象

ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(baos);
            // 序列化动态代理类
            out.writeObject(proxyInstance);
            out.flush();
            out.close();
  • 创建 ByteArrayOutputStreamObjectOutputStream 对象,用于序列化代理对象。
  • 将代理对象写入 ObjectOutputStream ,并刷新和关闭流。

# 反序列化动态代理对象

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bais);
            // 反序列化输入流数据为 FileSystem 对象
            FileSystem test = (FileSystem) in.readObject();
  • 创建 ByteArrayInputStreamObjectInputStream 对象,用于反序列化代理对象。
  • ObjectInputStream 读取对象,并将其转为 FileSystem 类型。

# 输出反序列化对象的信息

System.out.println("反序列化类实例类名:" + test.getClass());
            System.out.println("反序列化类实例toString:" + test.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 输出反序列化对象的类名和 toString 方法的结果。
  • 捕获并处理 IOExceptionClassNotFoundException 异常。

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

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

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

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

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

尘落 微信支付

微信支付

尘落 支付宝

支付宝

尘落 贝宝

贝宝