原理
从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编译源文件的资料请关注代码网其它相关文章!
发表评论