我们如何去动态的生成一个Java类

从常见的Java类去入手,我们通常的方式是开发一个Java代码,然后将Java代码去编译成class文件,然后通过类加载机制载入JVM,称为运行期间可以用的Java类了

所以最low的方法,就是讲Java文件生成一段源码,然后保存在文件等,启动ProcessBuilder,编译Java文件,进行类加载,进行运行时的加载

或者使用Java Compiler API,JDK 提供的标准API,里面提供了与javac对等的编译器功能.而且可以直接在其中交给类加载器去加载

直接写Java太麻烦,还需要编译保存,直接写Java字节码难度太高,所以最好有着对应的工具或者类库来实现,比如ASM Javassist cglib等

我们围绕类加载机制来进行展开,字节码和类加载机制的转换

字节码操作可以帮助我们做些什么

我们首先明白,类从字节码到Class对象的转换,类加载的基本方法如下

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的实现,只要能生成规范的字节码,不管作为byte数组的形式,还是放在ByteBuffer,都可以平滑的进行转换

而所有的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这个静态内部类,生成了字节码,并且利用btye数组保存,利用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

}

那么,JDK如何将老的类文件,分析生成新的字节码呢?

JDK的内部实现,是利用了java.lang.reflect.ProxyGenerator的内部实现.一种特有的字节码操作技术,利用了DataOutputStream的能力,配合hard-coded的各种JVM指令,生成字节码数组

但是因为过于高深,所以不建议使用

可以使用的有LamdaForm的字节码生成逻辑

java.lang.invoke.InvokerBytecodeGenerator

比如JDK自己的动态代理,就是在生成代理类的时候,利用ASM来生成了对应的新类

使用ASM的流程,可以简化的参考如下

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

cw.visit(V1_8,                      // 指定Java版本

ACC_PUBLIC,               // 说明是public类型

“com/mycorp/HelloProxy”,  // 指定包和类的名称

null,                     // 签名,null表示不是泛型

“java/lang/Object”,               // 指定父类

new String[]{ “com/mycorp/Hello” }); // 指定需要实现的接口

然后生成需要的方法和逻辑

MethodVisitor mv = cw.visitMethod(

ACC_PUBLIC,               // 声明公共方法

“sayHello”,               // 方法名称

“()Ljava/lang/Object;”,   // 描述符

null,                     // 签名,null表示不是泛型

null);                      // 可能抛出的异常,如果有,则指定字符串数组

mv.visitCode();

// 省略代码逻辑实现细节

cw.visitEnd();                      // 结束类字节码生成

这样,我们就创建了新的类

那么整体的流程讲解结束后,动态代理还可以应用在什么地方呢?

常见的Mock框架

ORM 框架

IOC 容器

Profiler 工具

形式化代码的工具

对于实际上需要基础字节码操作时候,可以利用Byte Buddy等

然后是java agent,可以进行拦截操作

利用拦截操作,动态的生成我们自定义的拦截类

https://www.cnblogs.com/rickiyang/p/11368932.html

发表评论

邮箱地址不会被公开。 必填项已用*标注