.
技术上可行的方式
生成源码文件 -> 编译源码 -> 加载类
方式1:javac 编译
可以用 ProcessBuilder 这个类启动 javac 进程,编译源码文件
方式2:Java Compiler API 编译
JDK中的 Java Compiler API 提供了与 javac 对等的编译能力。(示例:InMemoryJavaCompiler)
直接生成字节码 -> 加载类
一般通过字节码操作工具和类库来更改Java Class的字节码。
流行的工具有 ASM、Javassist、cglib、Byte Buddy 等。
很多工具/框架中的动态代理实现中就有用到这些工具。如,Spring框架内置了 cglib
字节码 -> Class
将字节码转换为 Class 发生在类加载过程中(《Java类加载》)。
可通过 ClassLoader.defineClass 方法将字节码转换成Class对象。
或者使用JDK中其它对等的方法(不同版本的JDK会略有不同)。
Java代码
-
public abstract class ClassLoader {
-
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);
-
-
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 动态代理实现方式
JDK动态代理实现的方式是比较hack的,需要了解JVM指令,偏移地址的处理也比较繁琐。
从 ProxyGenerator 类源码可以看出这种方式的使用门槛非常高,不适合普通开发场景。
更抽象方便的类库(ASM等)
JDK内部集成了ASM类库。如,命名空间 jdk.internal.org.objectweb.asm 下的 ClassWriter 类提供了一组用于生成字节码的方法。
Java代码
-
public class ClassWriter extends ClassVisitor {
-
// 构建 Class 签名(指定Java版本、访问权限、名称、父类、实现接口等)
-
public final void visit(
-
final int version, final int access, final String name,
-
final String signature, final String superName,
-
final String[] interfaces);
-
-
// 构建方法
-
public final MethodVisitor visitMethod(
-
final int access, final String name, final String descriptor,
-
final String signature, final String[] exceptions);
-
-
// 结束字节码生成
-
public final void visitEnd();
-
-
// 输出字节码,用于前述的 ClassLoader 载入该新建的类
-
public byte[] toByteArray();
-
...
-
}
-
-
public abstract class MethodVisitor {
-
// 构建方法内部代码
-
public void visitCode();
-
...
-
}
ASM中的 Visitor 模式
在生成/修改字节码的大多数场景中,都是依赖特定结构,修改或新增 方法、变量、类型等。
而Visitor模式的优势就是将算法和对象结构解耦,所以ASM API广泛使用了该模式。
字节码操作技术的使用场景
大多数业务不会直接使用字节码操作技术,但它是很多工具和底层框架必不可少的部分。
Mock 框架、ORM 框架、IOC 容器、Profiler或运行时诊断工具、形式化代码生成工具 等都会用到字节码操作技术。
在一些资源消耗统计的场景中也经常出现字节码操作技术的身影。
如,为了统计某些方法调用的网络通信的消耗,我们可以通过 JavaAgent + 字节码操作技术 来改变相关类,植入统计相关数据的代码。
这种方式不会侵入原业务代码,比AOP的方式更干净。而且不开启统计功能时,是零开销。