Java动态代理机制详解

本文深入探讨Java动态代理的原理、核心API(Proxy和InvocationHandler)及其常见应用场景,并通过代码示例演示如何利用动态代理实现方法调用日志功能。

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方法来转换成类对象的。

留言讨论

0 条留言

正在加载留言...