当前位置:首页 > 技术 > 正文内容

从零构建JDK动态代理核心机制

访客 技术 2026年8月31日 1

JDK动态代理的核心原理在于运行时动态生成代理类的字节码并将其加载到虚拟机中。通过自定义类加载器、反射机制以及动态源码生成与编译,我们可以手动还原这一过程。下面将详细拆解并实现一个极简版的JDK动态代理。

定义调用处理器接口

类似于原生的InvocationHandler,我们定义一个抽象接口,用于集中处理代理对象的方法调用,将实际执行逻辑委派给目标对象。

public interface CustomInvocationHandler {
    Object invoke(Object proxyInstance, Method targetMethod, Object[] arguments) throws Throwable;
}

核心代理工厂实现

代理工厂负责完成代理对象的组装,主要包含五个关键步骤:

  1. 动态拼接代理类的Java源码字符串。
  2. 将源码写入磁盘生成.java文件。
  3. 调用系统Java编译器将源码编译为.class文件。
  4. 通过自定义类加载器将class文件加载进JVM。
  5. 利用反射实例化代理对象并传入调用处理器。
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import javax.tools.StandardJavaFileManager;
import javax.tools.JavaFileObject;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

public class ProxyFactory {
    private static final String LINE_SEP = System.lineSeparator();

    public static Object createProxyInstance(DynamicClassLoader loader, Class<?>[] interfaces, CustomInvocationHandler handler) {
        try {
            // 1. 生成代理类源码
            String srcCode = buildSourceCode(interfaces[0]);
            String basePath = loader.getBasePath();
            File srcFile = new File(basePath, "$Proxy0.java");

            // 2. 写入.java文件
            Files.write(srcFile.toPath(), srcCode.getBytes(StandardCharsets.UTF_8));

            // 3. 编译源码
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(srcFile);
            compiler.getTask(null, fileManager, null, null, null, compilationUnits).call();
            fileManager.close();

            // 4. 加载class至JVM
            Class<?> proxyClazz = loader.findClass("$Proxy0");

            // 5. 实例化代理对象
            Constructor<?> constructor = proxyClazz.getConstructor(CustomInvocationHandler.class);
            srcFile.delete(); // 清理临时源文件
            return constructor.newInstance(handler);
        } catch (Exception e) {
            throw new RuntimeException("代理实例生成失败", e);
        }
    }

    private static String buildSourceCode(Class<?> intf) {
        StringBuilder sb = new StringBuilder();
        sb.append("import java.lang.reflect.Method;").append(LINE_SEP);
        sb.append("public class $Proxy0 implements ").append(intf.getName()).append(" {").append(LINE_SEP);
        sb.append("    private CustomInvocationHandler handler;").append(LINE_SEP);
        sb.append("    public $Proxy0(CustomInvocationHandler handler) {").append(LINE_SEP);
        sb.append("        this.handler = handler;").append(LINE_SEP);
        sb.append("    }").append(LINE_SEP);

        for (Method m : intf.getMethods()) {
            sb.append("    @Override").append(LINE_SEP);
            sb.append("    public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("() {").append(LINE_SEP);
            sb.append("        try {").append(LINE_SEP);
            sb.append("            Method targetMethod = ").append(intf.getName()).append(".class.getMethod(\"").append(m.getName()).append("\");").append(LINE_SEP);
            sb.append("            this.handler.invoke(this, targetMethod, null);").append(LINE_SEP);
            sb.append("        } catch (Throwable t) { t.printStackTrace(); }").append(LINE_SEP);
            sb.append("    }").append(LINE_SEP);
        }
        sb.append("}").append(LINE_SEP);
        return sb.toString();
    }
}

自定义类加载器

原生的类加载器无法直接加载我们动态生成的class字节码,因此需要重写findClass方法,将磁盘上的字节流转化为Class对象。

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class DynamicClassLoader extends ClassLoader {
    private final String basePath;

    public DynamicClassLoader(String basePath) {
        this.basePath = basePath;
    }

    public String getBasePath() {
        return this.basePath;
    }

    @Override
    protected Class<?> findClass(String name) {
        String classFilePath = basePath + File.separator + name + ".class";
        File classFile = new File(classFilePath);
        if (classFile.exists()) {
            try {
                byte[] classBytes = Files.readAllBytes(classFile.toPath());
                // classFile.delete(); // 实际场景中应清理生成的.class文件
                return defineClass(name, classBytes, 0, classBytes.length);
            } catch (IOException e) {
                throw new RuntimeException("类加载异常", e);
            }
        }
        return null;
    }
}

业务接口及实现

定义被代理的目标接口及其实现类。

public interface UserService {
    String getUserName();
}

public class UserServiceImpl implements UserService {
    @Override
    public String getUserName() {
        return "Admin_User";
    }
}

测试与执行

通过传入目标对象的类加载信息、接口数组以及调用处理器实例,即可生成并执行代理逻辑。

public class ProxyTest {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        String basePath = System.getProperty("user.dir");

        UserService proxy = (UserService) ProxyFactory.createProxyInstance(
            new DynamicClassLoader(basePath),
            target.getClass().getInterfaces(),
            (proxyInstance, method, params) -> {
                System.out.println("执行前增强逻辑...");
                Object result = method.invoke(target, params);
                System.out.println("执行后增强逻辑...");
                return result;
            }
        );
        proxy.getUserName();
    }
}

动态生成的代理类结构

上述代码运行时,会在磁盘上动态生成类似以下结构的$Proxy0类,它实现了目标接口,并在每个方法中通过反射将调用委派给CustomInvocationHandler

public class $Proxy0 implements UserService {
    private CustomInvocationHandler handler;
    public $Proxy0(CustomInvocationHandler handler) {
        this.handler = handler;
    }
    @Override
    public String getUserName() {
        try {
            Method targetMethod = UserService.class.getMethod("getUserName");
            return (String) this.handler.invoke(this, targetMethod, null);
        } catch (Throwable t) { 
            t.printStackTrace(); 
            return null;
        }
    }
}
标签: JDK动态代理

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。