Nashorn中如何将Java.extend生成类的字节码写入文件
首先明确:Java.extend()生成的Java子类/接口实现类是Nashorn运行时在内存中动态构造的,默认不会把字节码持久化到磁盘,常规的Class.getResourceAsStream()方式读不到对应的class文件资源,有两种可落地的实现方案:
方案1:开启Nashorn内置dump开关(零代码侵入,最推荐)
Nashorn本身自带了动态生成类的dump配置,只需要在JVM启动时加两个系统参数,就会自动把所有动态生成的适配器类字节码落到指定目录,完全不需要额外写业务逻辑:
# 开启动态生成类字节码dump -Dnashorn.dump.generated.classes=true # 配置字节码输出目录,不配置默认输出到当前工作目录下的nashorn_classes文件夹 -Dnashorn.dump.generated.classes.dir=./nashorn-dump-classes
这个参数是Nashorn原生支持的,不管是JDK8内置的Nashorn,还是后续独立维护的Nashorn版本都生效,所有
Java.extend、Java.asInterface生成的适配类都会自动被dump,不用你在脚本里手动拿Class对象处理。
方案2:运行时主动dump指定Class对象的字节码
如果你需要在代码运行过程中,拿到Java.extend(xxx, {...}).class的Class对象后主动导出字节码,可以基于Java Instrumentation API实现,步骤如下:
- 先实现一个Java Agent组件,拿到JVM的
Instrumentation实例 - 通过
ClassFileTransformer拦截类重转换过程,拿到目标类的原始字节码数组 - 拿到字节数组后直接通过文件输出流写入到
.class文件即可
核心Java侧工具代码参考:
import java.io.FileOutputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class NashornClassDumper { private static Instrumentation inst; // JVM启动时挂载Agent走这个入口 public static void premain(String args, Instrumentation inst) { NashornClassDumper.inst = inst; } // 运行时动态挂载Agent走这个入口 public static void agentmain(String args, Instrumentation inst) { NashornClassDumper.inst = inst; } public static byte[] getClassBytes(Class<?> targetClz) { final byte[][] bytesHolder = new byte[1][]; ClassFileTransformer transformer = new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain domain, byte[] classfileBuffer) { if (className.equals(targetClz.getName().replace('.', '/'))) { bytesHolder[0] = classfileBuffer.clone(); } // 返回null表示不修改类字节码,不影响类正常运行 return null; } }; try { inst.addTransformer(transformer, true); inst.retransformClasses(targetClz); } finally { inst.removeTransformer(transformer); } return bytesHolder[0]; } public static void dumpToFile(Class<?> targetClz, String outputPath) throws IOException { byte[] classBytes = getClassBytes(targetClz); if (classBytes == null) { throw new RuntimeException("获取目标类字节码失败,确认类是可重转换的"); } try (FileOutputStream fos = new FileOutputStream(outputPath)) { fos.write(classBytes); } } }
在Nashorn脚本里拿到目标类对象后,直接调用工具方法即可导出:
// 示例:用Java.extend生成ArrayList的子类 var MyList = Java.extend(java.util.ArrayList, { get: function(index) { return "test_" + index; } }); var targetClass = MyList.class; // 调用工具类导出字节码到文件 var dumper = Java.type('cn.yourpackage.NashornClassDumper'); dumper.dumpToFile(targetClass, "./MyList.class");
注意避坑
不要尝试通过targetClass.getResourceAsStream(targetClass.getSimpleName() + ".class")的方式拿字节码,Nashorn动态生成类是直接调用ClassLoader.defineClass()传入内存中的字节数组定义的,类加载器不会持有原始字节码,也不会在类路径下生成对应的class资源,这种方式拿到的流永远是null。
内容的提问来源于stack exchange,提问作者BangBang

