原理
从java 6开始,引入了java代码重写过的编译器接口,使得我们可以在运行时编译java源代码,然后再通过类加载器将编译好的类加载进jvm,这种在运行时编译代码的操作就叫做动态编译。
主要类库
javacompiler -表示java编译器, run方法执行编译操作. 还有一种编译方式是先生成编译任务(compilationtask), 让后调用compilationtask的call方法执行编译任务
javafileobject -表示一个java源文件对象
javafilemanager - java源文件管理类, 管理一系列javafileobject
diagnostic -表示一个诊断信息
diagnosticlistener -诊断信息监听器, 编译过程触发. 生成编译task(javacompiler#gettask())或获取filemanager(javacompiler#getstandardfilemanager())时需要传递diagnosticlistener以便收集诊断信息
流程图

源码文件 -> 字节码文件
public static void fromjavafile() {
// 获取javac编译器对象
javacompiler compiler = toolprovider.getsystemjavacompiler();
// 获取文件管理器:负责管理类文件的输入输出
standardjavafilemanager filemanager = compiler.getstandardfilemanager(null, null, null);
// 获取要被编译的java源文件
file file = new file("/users//github/perfect/perfect-javassist/src/main/java/com/jc/javassist/compiler/testhello.java");
// 通过源文件获取到要编译的java类源码迭代器,包括所有内部类,其中每个类都是一个javafileobject
iterable<? extends javafileobject> compilationunits = filemanager.getjavafileobjects(file);
// 生成编译任务
javacompiler.compilationtask task = compiler.gettask(null, filemanager, null, null, null, compilationunits);
// 执行编译任务
task.call();
}
我们这里准备了testhello.java
public class testhello {
public static void main(string[] args) {
system.out.println("this is a test");
}
}
我们试着手动加载该class文件,使用类加载器的defineclass方法,可以直接加载字节码文件。
public static class<?> loadclassfromdisk(string path) throws nosuchmethodexception, invocationtargetexception, illegalaccessexception {
// defineclass 为 classloader 类的一个方法,用于加载类
// 但是这个方法是 protected 的,所以需要通过反射的方式获取这个方法的权限
class<classloader> classloaderclass = classloader.class;
method defineclass = classloaderclass.getdeclaredmethod("defineclass", byte[].class, int.class, int.class);
defineclass.setaccessible(true);
// 读取文件系统的 file 为 byte 数组
file file = new file(path);
byte[] bytes = new byte[(int) file.length()];
try (fileinputstream fileinputstream = new fileinputstream(file)) {
fileinputstream.read(bytes);
} catch (ioexception e) {
e.printstacktrace();
}
// 执行 defineclass 方法 返回 class 对象
return (class<?>) defineclass.invoke(thread.currentthread().getcontextclassloader(), bytes, 0, bytes.length);
}
由于testhello中的方法为静态方法,使用class反射机制执行方法
// 执行编译任务
boolean call = task.call();
if (call) {
class<?> o = loadclassfromdisk("/users/oneyoung/oneyoung/project/my/code/src/main/java/top/oneyoung/dynamic/testhello.class");
method main = o.getmethod("main", string[].class);
main.invoke(null, new object[]{new string[]{}});
}
执行结果
| this is a test
源码字符串 -> 字节码文件
在流程图中,gettask().call()会通过调用作为参数传入的javafileobject对象的getcharcontent()方法获得字符串序列,即源码的读取是通过 javafileobject的 getcharcontent()方法,那我们只需要重写getcharcontent()方法,即可将我们的字符串源码装进javafileobject了。构造sourcejavafileobject实现定制的javafileobject对象,用于存储字符串源码
public class sourcejavafileobject extends simplejavafileobject {
/**
* the source code of this "file".
*/
private final string code;
sourcejavafileobject(string name, string code) {
super(uri.create("string:///" + name.replace('.', '/') + kind.source.extension), kind.source);
this.code = code;
}
@override
public charsequence getcharcontent(boolean ignoreencodingerrors) {
return code;
}
}
则创建javafileobject对象时,变为了:
// 通过源代码字符串获取到要编译的java类源码迭代器,包括所有内部类,其中每个类都是一个javafileobject
sourcejavafileobject javafileobject = new sourcejavafileobject("testhello", "public class testhello { public static void main(string[] args) { system.out.println("hello world"); } }");
// 生成编译任务
javacompiler.compilationtask task = compiler.gettask(null, filemanager, null, null, null, collections.singleton(javafileobject));
执行后,同样编译出了class文件,不过由于没有指定编译的class输出路径,他会默认放在源文件的根目录下
源码字符串 -> 字节码数组
如果我们进行动态编译时,想要直接输入源码字符串并且输出的是字节码数组,而不是输出字节码文件,又该如何实现?实际上,这是从内存中得到源码,再输出到内存的方式。
在gettask().call()源代码执行流程图中,我们可以发现javafileobject 的 openoutputstream()方法控制了编译后字节码的输出行为,编译完成后会调用openoutputstream获取输出流,并写数据(字节码)。所以我们需要重写javafileobject 的 openoutputstream()方法。
同时在执行流程图中,我们还发现用于输出的javafileobject 对象是javafilemanager的getjavafileforoutput()方法提供的,所以为了让编译器编译完成后,将编译得到的字节码输出到我们自己构造的javafileobject 对象,我们还需要自定义javafilemanager。
这里我使用类委托的方式,把大部分功能委托给了传入的standardjavafilemanager,主要是重写了getjavafileforoutput,使输出编译完成的字节码文件为字节数组。
然后增加了方法getbytesbyclassname获取编译完成的字节码字节数组
public class bytearrayjavafilemanager implements javafilemanager {
private static final logger log = loggerfactory.getlogger(bytearrayjavafilemanager.class);
private final standardjavafilemanager filemanager;
/**
* synchronizing due to concurrentmodificationexception
*/
private final map<string, bytearrayoutputstream> buffers = collections.synchronizedmap(new linkedhashmap<>());
public bytearrayjavafilemanager(standardjavafilemanager filemanager) {
this.filemanager = filemanager;
}
@override
public classloader getclassloader(location location) {
return filemanager.getclassloader(location);
}
@override
public synchronized iterable<javafileobject> list(location location, string packagename, set<kind> kinds, boolean recurse) throws ioexception {
return filemanager.list(location, packagename, kinds, recurse);
}
@override
public string inferbinaryname(location location, javafileobject file) {
return filemanager.inferbinaryname(location, file);
}
@override
public boolean issamefile(fileobject a, fileobject b) {
return filemanager.issamefile(a, b);
}
@override
public synchronized boolean handleoption(string current, iterator<string> remaining) {
return filemanager.handleoption(current, remaining);
}
@override
public boolean haslocation(location location) {
return filemanager.haslocation(location);
}
@override
public javafileobject getjavafileforinput(location location, string classname, kind kind) throws ioexception {
if (location == standardlocation.class_output) {
boolean success;
final byte[] bytes;
synchronized (buffers) {
success = buffers.containskey(classname) && kind == kind.class;
bytes = buffers.get(classname).tobytearray();
}
if (success) {
return new simplejavafileobject(uri.create(classname), kind) {
@override
public inputstream openinputstream() {
return new bytearrayinputstream(bytes);
}
};
}
}
return filemanager.getjavafileforinput(location, classname, kind);
}
@override
public javafileobject getjavafileforoutput(location location, final string classname, kind kind, fileobject sibling) {
return new simplejavafileobject(uri.create(classname), kind) {
@override
public outputstream openoutputstream() {
// 字节输出流用与filemanager输出编译完成的字节码文件为字节数组
bytearrayoutputstream bos = new bytearrayoutputstream();
// 将每个需要加载的类的输出流进行缓存
buffers.putifabsent(classname, bos);
return bos;
}
};
}
@override
public fileobject getfileforinput(location location, string packagename, string relativename) throws ioexception {
return filemanager.getfileforinput(location, packagename, relativename);
}
@override
public fileobject getfileforoutput(location location, string packagename, string relativename, fileobject sibling) throws ioexception {
return filemanager.getfileforoutput(location, packagename, relativename, sibling);
}
@override
public void flush() {
// do nothing
}
@override
public void close() throws ioexception {
filemanager.close();
}
@override
public int issupportedoption(string option) {
return filemanager.issupportedoption(option);
}
public void clearbuffers() {
buffers.clear();
}
public map<string, byte[]> getallbuffers() {
map<string, byte[]> ret = new linkedhashmap<>(buffers.size() * 2);
map<string, bytearrayoutputstream> compiledclasses = new linkedhashmap<>(ret.size());
synchronized (buffers) {
compiledclasses.putall(buffers);
}
compiledclasses.foreach((k, v) -> ret.put(k, v.tobytearray()));
return ret;
}
public byte[] getbytesbyclassname(string classname) {
return buffers.get(classname).tobytearray();
}
}
然后我们修改下之前的执行流程
public static void fromjavasourcetobytearray1() throws exception {
// 获取javac编译器对象
javacompiler compiler = toolprovider.getsystemjavacompiler();
// 获取文件管理器:负责管理类文件的输入输出
standardjavafilemanager filemanager = compiler.getstandardfilemanager(null, null, null);
// 创建自定义的filemanager
bytearrayjavafilemanager bytearrayjavafilemanager = new bytearrayjavafilemanager(filemanager);
// 通过源代码字符串获取到要编译的java类源码迭代器,包括所有内部类,其中每个类都是一个javafileobject
javafileobject javafileobject = new sourcejavafileobject("testhello", "public class testhello { public static void say(string args) { system.out.println(args); } }");
javacompiler.compilationtask task = compiler.gettask(null, bytearrayjavafilemanager, null, null, null, collections.singletonlist(javafileobject));
// 执行编译任务
boolean call = task.call();
if (boolean.true.equals(call)) {
byte[] testhellos = bytearrayjavafilemanager.getbytesbyclassname("testhello");
class<classloader> classloaderclass = classloader.class;
method defineclass = classloaderclass.getdeclaredmethod("defineclass", byte[].class, int.class, int.class);
defineclass.setaccessible(true);
object invoke = defineclass.invoke(testhello.class.getclassloader(), testhellos, 0, testhellos.length);
class clazz = (class) invoke;
clazz.getmethod("say", string.class).invoke(null, "你好");
}
}
以上就是java代码实现编译源文件的详细内容,更多关于java编译源文件的资料请关注代码网其它相关文章!
发表评论