如何获取Unsafe#defineClass加载的运行时生成类的字节码文件
导出Unsafe#defineClass生成的运行时类字节码
因为用Unsafe#defineClass加载的类,字节码不会被类加载器存储到常规的资源查找路径中,所以getResourceAsStream自然拿不到。以下几种方法可以解决:
方法1:使用Instrumentation API获取已加载类的字节码
Instrumentation可以获取所有已加载类的字节码,不管类是通过什么方式加载的。你需要写一个Java Agent:
步骤1:实现Agent类
import java.io.FileOutputStream; import java.io.IOException; import java.lang.instrument.Instrumentation; public class ClassExporterAgent { // 启动时加载Agent public static void premain(String agentArgs, Instrumentation inst) { exportTargetClass(inst, "your.target.class.FullQualifiedName"); // 替换成目标类的全限定名 } // 附加到运行中进程时用 public static void agentmain(String agentArgs, Instrumentation inst) { exportTargetClass(inst, "your.target.class.FullQualifiedName"); } private static void exportTargetClass(Instrumentation inst, String targetClassName) { for (Class<?> clazz : inst.getAllLoadedClasses()) { if (clazz.getName().equals(targetClassName)) { try { byte[] classBytes = inst.getClassFileBytes(clazz); String outputPath = clazz.getSimpleName() + ".class"; try (FileOutputStream fos = new FileOutputStream(outputPath)) { fos.write(classBytes); System.out.println("字节码已导出至: " + outputPath); } } catch (IOException e) { e.printStackTrace(); } break; } } } }
步骤2:打包Agent
创建MANIFEST.MF文件,内容如下:
Premain-Class: ClassExporterAgent Agent-Class: ClassExporterAgent Can-Redefine-Classes: true
用jar命令打包:jar cvfm exporter-agent.jar MANIFEST.MF ClassExporterAgent.class
步骤3:运行程序
- 启动时加载Agent:
java -javaagent:exporter-agent.jar -jar your-app.jar - 附加到运行中进程:用JDK的
jcmd工具,比如jcmd <pid> load exporter-agent.jar
方法2:拦截Unsafe#defineClass方法保存字节码
如果能拦截到Unsafe的defineClass调用,就可以在类被加载前直接保存字节码。用ByteBuddy可以快速实现:
import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.ByteBuddy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.matcher.ElementMatchers; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; public class UnsafeClassExporter { public static void main(String[] args) throws Exception { // 安装ByteBuddy Agent ByteBuddyAgent.install(); // 获取Unsafe实例(因为Unsafe.getUnsafe()有访问限制) Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null); // 拦截Unsafe的defineClass方法 new ByteBuddy() .redefine(sun.misc.Unsafe.class) .method(ElementMatchers.named("defineClass") .and(ElementMatchers.takesArguments(String.class, byte[].class, int.class, int.class, ClassLoader.class, java.security.ProtectionDomain.class))) .intercept(MethodDelegation.to(UnsafeClassExporter.class)) .make() .load(sun.misc.Unsafe.class.getClassLoader()); // 这里执行你的业务代码,触发类生成逻辑 } // 拦截方法,保存字节码后调用原方法 public static Class<?> intercept(Method originalMethod, String className, byte[] classBytes, int offset, int length, ClassLoader loader, java.security.ProtectionDomain pd) throws Exception { // 保存字节码到文件 try (FileOutputStream fos = new FileOutputStream(className + ".class")) { fos.write(classBytes, offset, length); System.out.println("已保存类字节码: " + className + ".class"); } catch (IOException e) { e.printStackTrace(); } // 调用原defineClass方法 return (Class<?>) originalMethod.invoke(sun.misc.Unsafe.getUnsafe(), className, classBytes, offset, length, loader, pd); } }
注意:需要引入ByteBuddy的依赖,比如Maven:
<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.11</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.14.11</version> </dependency>
方法3:直接修改类生成逻辑(仅限可控代码)
如果是你自己的代码调用Unsafe#defineClass,那最简单的方式就是在调用前把字节码写入文件:
byte[] classBytes = ...; // 生成的类字节码 // 先保存字节码 try (FileOutputStream fos = new FileOutputStream("GeneratedClass.class")) { fos.write(classBytes); } // 再调用defineClass Class<?> generatedClass = unsafe.defineClass(null, classBytes, 0, classBytes.length, null, null);
内容的提问来源于stack exchange,提问作者uglibubla
相关产品推荐
相关产品推荐

