如何动态生成一个Java类
image
开发者编写Java 代码,调用 javac 编译生成 class 文件。然后通过类加载机制载入 JVM ,即成为了应用可以使用的 Java 类。
可以写一个编译类编译 java 代码:
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Method;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* 动态生成类以及编译,JDK版本必须要在1.6,或者1.6以上
*/
public class CompilerTest {
//回车加换行符
static String rt = "\r\n";
//生成类的源文件,写成字符串的形式
static String src =
"package com.test;"+
"public class HelloWorld {" + rt +
" public static void main(String[] args) {" + rt +
" System.out.println(\"Hello world!\");" + rt +
" }" + rt +
"}";
public static void main (String[] args) throws Exception {
//写文件,目录可以自己定义
String filename = System.getProperty ("user.dir") + "/src/HelloWorld.java";
//System.out.println (filename);
File file = new File (filename);
FileWriter fw = new FileWriter (file);
fw.write(src);
fw.flush();
fw.close();
//编译文件,调用jdk本身的工具
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println(compiler.getClass().getName());
StandardJavaFileManager sjfm = compiler.getStandardFileManager (null, null, null);
Iterable units = sjfm.getJavaFileObjects (filename);
CompilationTask ct = compiler.getTask (null, sjfm, null, null, null, units);
// 动态编译可执行的代码
ct.call();
Class<?> clazz = Class.forName("com.test.HelloWorld");
try {
// 生成对象
Object obj = clazz.newInstance();
Class<? extends Object> cls = obj.getClass();
// 调用main方法
Method m = clazz.getMethod("main",String[].class);
Object invoke = m.invoke(obj, new Object[] { new String[] {} });
} catch (Exception e) {
e.printStackTrace();
}
sjfm.close();
}
}
字节码如何转换成Class 对象
类加载过程,这一步是通过 defineClass 方法实现的。将字节码转换成 Class 对象。defineClass 最终都是本地代码实现的。
protected fnal Class<?> defneClass(String name, byte[] b, int of, int len,ProtectionDomain protectionDomain)
protected fnal Class<?> defneClass(String name, java.nio.ByteBufer b,ProtectionDomain protectionDomain)
JDK Proxy的实现
JDK dynamic Proxy,JDK动态代理的实现逻辑在 ProxyBuilder 这个静态内部类中 ,ProxyGenerator 生成字节码,并 byte 数组的形式保存,然后通过调用 Unsafe 提供的 defineClass 入库。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
Class<?> pc = UNSAFE.defneClass(proxyName, proxyClassFile,
0, proxyClassFile.length,loader, null);
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
// 如果出现ClassFormatError,很可能是输入参数有问题,比如, ProxyGenerator有bug
http://hg.openjdk.java.net/jdk/jdk/file/29169633327c/src/java.base/share/classes/java/lang/reflect/Proxy.java
JDK 动态代理是如何生成字节码的?
主要通过 java.lang.reflect.ProxyGenerator 的内部实现。
private byte[] generateClassFile() {
//第一步, 将所有的方法组装成ProxyMethod对象
//首先为代理类生成toString, hashCode, equals等代理方法
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
//遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
//对于具有相同签名的代理方法, 检验方法的返回值是否兼容
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
//第二步, 组装要生成的class文件的所有的字段信息和方法信息
try {
//添加构造器方法
methods.add(generateConstructor());
//遍历缓存中的代理方法
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
//添加代理类的静态字段, 例如:private static Method m1;
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
//添加代理类的代理方法
methods.add(pm.generateMethod());
}
}
//添加代理类的静态字段初始化方法
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
//验证方法和字段集合不能大于65535
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
//第三步, 写入最终的class文件
//验证常量池中存在代理类的全限定名
cp.getClass(dotToSlash(className));
//验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy"
cp.getClass(superclassName);
//验证常量池存在代理类接口的全限定名
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
//接下来要开始写入文件了,设置常量池只读
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
//1.写入魔数
dout.writeInt(0xCAFEBABE);
//2.写入次版本号
dout.writeShort(CLASSFILE_MINOR_VERSION);
//3.写入主版本号
dout.writeShort(CLASSFILE_MAJOR_VERSION);
//4.写入常量池
cp.write(dout);
//5.写入访问修饰符
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
//6.写入类索引
dout.writeShort(cp.getClass(dotToSlash(className)));
//7.写入父类索引, 生成的代理类都继承自Proxy
dout.writeShort(cp.getClass(superclassName));
//8.写入接口计数值
dout.writeShort(interfaces.length);
//9.写入接口集合
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
}
//10.写入字段计数值
dout.writeShort(fields.size());
//11.写入字段集合
for (FieldInfo f : fields) {
f.write(dout);
}
//12.写入方法计数值
dout.writeShort(methods.size());
//13.写入方法集合
for (MethodInfo m : methods) {
m.write(dout);
}
//14.写入属性计数值, 代理类class文件没有属性所以为0
dout.writeShort(0);
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
//转换成二进制数组输出
return bout.toByteArray();
}
http://hg.openjdk.java.net/jdk/jdk/file/29169633327c/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java
https://docs.oracle.com/javase/9/docs/api/java/io/DataOutputStream.html
JDK 动态代理的实现
提供一个基础的接口,作为调用类型和代理类之间的统一入库。如 com.test.Hello。
实现 InvocationHandler ,对代理对象方法的调用,会被分派到其 invoke 方法来真正实现动作。
通过 Proxy 类,调用 newProxyInstance 方法,升一个实现了相应基础接口的代理类实现。
public satic Object newProxyInsance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
https://docs.oracle.com/javase/9/docs/api/java/lang/reflect/InvocationHandler.html