【JAVA】不会有人不知道 Java 类能够在运行时动态生成吧? 您所在的位置:网站首页 java动态编译加载类框架 【JAVA】不会有人不知道 Java 类能够在运行时动态生成吧?

【JAVA】不会有人不知道 Java 类能够在运行时动态生成吧?

2024-03-28 03:37| 来源: 网络整理| 查看: 265

前言

在阅读本文之前,可以先回看一下这篇博文:【JAVA】动态代理基于什么原理?

本篇博文的重点是,有哪些方法可以在运行时动态生成一个 Java 类?

概述

我们可以从常见的 Java 类来源分析,通常的开发过程是,开发者编写 Java 代码,调用 javac 编译成 class 文件,然后通过类加载机制载入 JVM,就成为应用运行时可以使用的 Java 类了。

从上面过程得到启发,其中一个直接的方式是从源码入手,可以利用 Java 程序生成一段源码,然后保存到文件等,下面就只需要解决编译问题了。

有一种笨办法,直接用 ProcessBuilder 之类启动 javac 进程,并指定上面生成的文件作为输入,进行编译。最后,再利用类加载器,在运行时加载即可。

前面的方法,本质上还是在当前程序进程之外编译的,那么还有没有不这么 low 的办法呢?

你可以考虑使用 Java Compiler API,这是 JDK 提供的标准 API,里面提供了与 javac 对等的编译器功能,具体请参考 java.compiler 相关文档。

进一步思考,我们一直围绕 Java 源码编译成为 JVM 可以理解的字节码,换句话说,只要是符合 JVM 规范的字节码,不管它是如何生成的,是不是都可以被 JVM 加载呢?我们能不能直接生成相应的字节码,然后交给类加载器去加载呢?当然也可以,不过直接去写字节码难度太大,通常我们可以利用 Java 字节码操纵工具和类库来实现.  

正文

首先来理解一下,类从字节码到 Class 对象的转换,在类加载过程中,这一步是通过下面的方法提供的功能,或者 defineClass 的其他本地对等实现。

protected final Class defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) protected final Class defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain)

这里只选取了最基础的两个典型的 defineClass 实现,Java 重载了几个不同的方法。

可以看出,只要能够生成出规范的字节码,不管是作为 byte 数组的形式,还是放到 ByteBuffer 里,都可以平滑地完成字节码到 Java 对象的转换过程。

JDK 提供的 defineClass 方法,最终都是本地代码实现的。

static native Class defineClass1(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd, String source); static native Class defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);

更进一步,我们来看看 JDK dynamic proxy 的实现代码。你会发现,对应逻辑是实现在 ProxyBuilder 这个静态内部类中,ProxyGenerator 生成字节码,并以 byte 数组的形式保存,然后通过调用 Unsafe 提供的 defineClass 入口。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags); try { Class pc = UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length, loader, null); reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE); return pc; } catch (ClassFormatError e) {. // 如果出现ClassFormatError,很可能是输入参数有问题,比如,ProxyGenerator 有 bug }

前面理顺了二进制的字节码信息到 Class 对象的转换过程,似乎我们还没有分析如何生成自己需要的字节码,接下来一起来看看相关的字节码操纵逻辑。

JDK 内部动态代理的逻辑,可以参考 java.lang.reflect.ProxyGenerator 的内部实现。我觉得可以认为这是种另类的字节码操纵技术,其利用了 DataOutputStrem 提供的能力,配合 hard-coded 的各种 JVM 指令实现方法,生成所需的字节码数组。

private void codeLocalLoadStore(int lvar, int opcode, int opcode_0, DataOutputStream out) throws IOException { assert lvar >= 0 && lvar


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有