【Java核心-进阶】运行时动态生成Java类

.

技术上可行的方式

生成源码文件 -> 编译源码 -> 加载类

方式1:javac 编译

可以用 ProcessBuilder 这个类启动 javac 进程,编译源码文件

方式2:Java Compiler API 编译

JDK中的 Java Compiler API 提供了与 javac 对等的编译能力。(示例:InMemoryJavaCompiler

直接生成字节码 -> 加载类

一般通过字节码操作工具和类库来更改Java Class的字节码。

流行的工具有 ASMJavassistcglibByte Buddy 等。

很多工具/框架中的动态代理实现中就有用到这些工具。如,Spring框架内置了 cglib

字节码 -> Class

将字节码转换为 Class 发生在类加载过程中(《Java类加载》)。

可通过 ClassLoader.defineClass 方法将字节码转换成Class对象。

或者使用JDK中其它对等的方法(不同版本的JDK会略有不同)。

Java代码

 

  1. public abstract class ClassLoader {  

  2.   protected final Class<?> defineClass(  

  3.       String name, byte[] b, int off, int len,  

  4.       ProtectionDomain protectionDomain);  

  5.   

  6.   protected final Class<?> defineClass(  

  7.       String name, java.nio.ByteBuffer b,  

  8.       ProtectionDomain protectionDomain);  

  9.   

  10.   static native Class<?> defineClass1(  

  11.       ClassLoader loader, String name, byte[] b, int off, int len,  

  12.       ProtectionDomain pd, String source);  

  13.   

  14.   static native Class<?> defineClass2(  

  15.       ClassLoader loader, String name, java.nio.ByteBuffer b,  

  16.       int off, int len, ProtectionDomain pd, String source);  

  17.   ...  

  18. }  

操作字节码

JDK 动态代理实现方式

JDK动态代理实现的方式是比较hack的,需要了解JVM指令,偏移地址的处理也比较繁琐。

ProxyGenerator 类源码可以看出这种方式的使用门槛非常高,不适合普通开发场景。

更抽象方便的类库(ASM等)

JDK内部集成了ASM类库。如,命名空间 jdk.internal.org.objectweb.asm 下的 ClassWriter 类提供了一组用于生成字节码的方法。

Java代码

 

  1. public class ClassWriter extends ClassVisitor {  

  2.   // 构建 Class 签名(指定Java版本、访问权限、名称、父类、实现接口等)  

  3.   public final void visit(  

  4.       final int version, final int access, final String name,  

  5.       final String signature, final String superName,  

  6.       final String[] interfaces);  

  7.   

  8.   // 构建方法  

  9.   public final MethodVisitor visitMethod(  

  10.       final int access, final String name, final String descriptor,  

  11.       final String signature, final String[] exceptions);  

  12.   

  13.   // 结束字节码生成  

  14.   public final void visitEnd();  

  15.   

  16.   // 输出字节码,用于前述的 ClassLoader 载入该新建的类  

  17.   public byte[] toByteArray();  

  18.   ...  

  19. }  

  20.   

  21. public abstract class MethodVisitor {  

  22.   // 构建方法内部代码  

  23.   public void visitCode();  

  24.   ...  

  25. }  

ASM中的 Visitor 模式

在生成/修改字节码的大多数场景中,都是依赖特定结构,修改或新增 方法、变量、类型等。

而Visitor模式的优势就是将算法和对象结构解耦,所以ASM API广泛使用了该模式。

字节码操作技术的使用场景

大多数业务不会直接使用字节码操作技术,但它是很多工具和底层框架必不可少的部分。

Mock 框架、ORM 框架、IOC 容器、Profiler或运行时诊断工具、形式化代码生成工具 等都会用到字节码操作技术。

在一些资源消耗统计的场景中也经常出现字节码操作技术的身影。

如,为了统计某些方法调用的网络通信的消耗,我们可以通过 JavaAgent + 字节码操作技术 来改变相关类,植入统计相关数据的代码。

这种方式不会侵入原业务代码,比AOP的方式更干净。而且不开启统计功能时,是零开销。

发布了219 篇原创文章 · 获赞 3 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/hchaoh/article/details/103906335