java Compiler API (java编译api)
在早期的版本中(Java SE5及以前版本)中只能通過tools.jar中的com.sun.tools.javac包來調(diào)用Java編譯器,但由于tools.jar不是標準的Java庫,在使用時必須要設(shè)置這個jar的路徑。而在Java SE6中為我們提供了標準的包來操作Java編譯器,這就是javax.tools包。
編譯java文件
使用Java API來編譯Java源代碼有非常多方法,目前讓我們來看一種最簡單的方法,通過JavaCompiler進行編譯。
使用ToolProvider.getSystemJavaCompiler來得到一個JavaCompiler接口的實例。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();JavaCompiler中最核心的方法是run()。通過這個方法能編譯java源代碼。
int run(InputStream in, OutputStream out, OutputStream err, String... arguments)參數(shù)分別用來為:
如果run編譯成功,返回? 0。
如果前3個參數(shù)傳入的是null,那么run方法將以標準的輸入、輸出代替,即System.in、System.out和System.err。如果我們要編譯一個test.java文件,并將使用標準輸入輸出,run的使用方法如下:
int results = tool.run(null, null, null, "F:\\demo\\Test.java");完整的例子:
//CompileMain.java import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class CompileMain {public static void main(String[] args) throws IOException {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, "F:\\demo\\Test.java");System.out.println(result == 0 ? "編譯成功" : "編譯失敗");//執(zhí)行java 命令 , 空參數(shù), 所在文件夾Process process = Runtime.getRuntime().exec("java Test",null,new File("F:\\demo\\"));BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));String str;while ((str = bufferedReader.readLine()) != null) {System.out.println(str);}} } public class Test {public static void main(String[] args) {System.out.println("this is a test.java file ,thank you very much");}} $ javac CompileMain.java $ java CompileMain 編譯成功 this is a test.java file ,thank you very much編譯非文件形式源代碼
JDK 6 的編譯器 API 的另外一個強大之處在于,它可以編譯的源文件的形式并不局限于文本文件。JavaCompiler?類依靠文件管理服務(wù)可以編譯多種形式的源文件。比如直接由內(nèi)存中的字符串構(gòu)造的文件,或者是從數(shù)據(jù)庫中取出的文件。這種服務(wù)是由?JavaFileManager?類提供的。
在Java SE6中最佳的方法是使用StandardJavaFileManager類。這個類能非常好地控制輸入、輸出,并且能通過DiagnosticListener得到診斷信息,而DiagnosticCollector類就是listener的實現(xiàn)。新的 JDK 定義了?javax.tools.FileObject?和?javax.tools.JavaFileObject?接口。任何類,只要實現(xiàn)了這個接口,就可以被?JavaFileManager?識別。

使用StandardJavaFileManager步驟:
在使用這種方法調(diào)用Java編譯時最復(fù)雜的方法就是getTask,下面讓我們討論一下getTask方法。這個方法有如下所示的6個參數(shù)。
getTask(Writer out,JavaFileManager fileManager,DiagnosticListener<? super JavaFileObject> diagnosticListener,Iterable<String> options,Iterable<String> classes,Iterable<? extends JavaFileObject> compilationUnits)這些參數(shù)大多數(shù)都可為null。他們的含義所下。
- out: 用于輸出錯誤的流,默認是System.err。
- fileManager:標準的文件管理。
- diagnosticListener: 編譯器的默認行為。
- options: 編譯器的選項
- classes:參和編譯的class。
- compilationUnits: 待編譯的Java文件,不能為null。
CompilationTask?提供了?setProcessors(Iterable<? extends Processor>processors)方法,用戶可以制定處理 annotation 的處理器。
在使用完getTask前,需要通過StandardJavaFileManager.getJavaFileObjectsFromFiles()或StandardJavaFileManager.getJavaFileObjectsFromStrings方法得到待編譯的compilationUnits對象。
也可以通過繼承/實現(xiàn)SimpleJavaObject獲取帶編譯的對象。
調(diào)用這兩個方法的方式如下:
Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names)String[] filenames = …; Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames));JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);最后需要關(guān)閉fileManager.close();
例如:
package win.hgfdodo.dynamic;import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.util.Arrays;public class JavaFileManagerMain {public static void main(String[] args) {String fullQuanlifiedFileName = "win.hgfdodo.dynamic.".replaceAll("\\.", java.io.File.separator) + "Calculator.java";JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager fileManager =compiler.getStandardFileManager(null, null, null);Iterable<? extends JavaFileObject> files =fileManager.getJavaFileObjectsFromStrings(Arrays.asList(fullQuanlifiedFileName));JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, files);Boolean result = task.call();if (result == true) {System.out.println("Succeeded");}} } package win.hgfdodo.dynamic;public class Calculator {public int multiply(int multiplicand, int multiplier) {return multiplicand * multiplier;} }JavaFileObject獲取java源程序
開發(fā)者希望生成?Calculator?的一個測試類,而不是手工編寫。使用 compiler API,可以將內(nèi)存中的一段字符串,編譯成一個 CLASS 文件。
定制 JavaFileObject 對象:
package win.hgfdodo.dynamic;import javax.tools.SimpleJavaFileObject; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException;public class StringObject extends SimpleJavaFileObject {private String content = null;protected StringObject(String className, String contents) throws URISyntaxException {super(new URI(className), Kind.SOURCE);this.content = contents;}@Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {return content;} }SimpleJavaFileObject?是?JavaFileObject?的子類,它提供了默認的實現(xiàn)。繼承?SimpleJavaObject?之后,只需要實現(xiàn)?getCharContent?方法。
接下來,在內(nèi)存中構(gòu)造?Calculator?的測試類?CalculatorTest,并將代表該類的字符串放置到?StringObject?中,傳遞給?JavaCompiler.getTask?方法。
具體如下:
package win.hgfdodo.dynamic;import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.net.URISyntaxException; import java.util.Arrays;public class StringClassCompilerMain {public static void main(String[] args) {JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);JavaFileObject testFile = generateTest();Iterable<? extends JavaFileObject> classes = Arrays.asList(testFile);JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, null, null, null, classes);if(task.call()){System.out.println("success");}else{System.out.println("failure!");}}private static JavaFileObject generateTest() {String contents = new String("package win.hgfdodo.dynamic;" +"class CalculatorTest {\n" +" public void testMultiply() {\n" +" Calculator c = new Calculator();\n" +" System.out.println(c.multiply(2, 4));\n" +" }\n" +" public static void main(String[] args) {\n" +" CalculatorTest ct = new CalculatorTest();\n" +" ct.testMultiply();\n" +" }\n" +"}\n");StringObject so = null;try {so = new StringObject("win.hgfdodo.dynamic.CalculatorTest", contents);} catch (URISyntaxException e) {e.printStackTrace();}return so;} }采集編譯器的診斷信息
收集編譯過程中的診斷信息是JDK6新增的內(nèi)容。診斷信息,通常指錯誤、警告或是編譯過程中的詳盡輸出。
JDK 6 通過?Listener?機制,獲取這些信息。如果要注冊一個?DiagnosticListener,必須使用?CompilationTask?來進行編譯,因為?Tool.run?方法沒有辦法注冊?Listener。
步驟:
例子:
package win.hgfdodo.dynamic;import javax.tools.*; import java.net.URISyntaxException; import java.util.Arrays; import java.util.List; import java.util.Locale;public class StringClassCompilerMain {public static void main(String[] args) {JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);JavaFileObject testFile = generateTest();Iterable<? extends JavaFileObject> classes = Arrays.asList(testFile);JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, collector, null, null, classes);if(task.call()){System.out.println("success");}else{System.out.println("failure!");}List<Diagnostic<? extends JavaFileObject>> diagnostics = collector.getDiagnostics();for (Diagnostic<? extends JavaFileObject> diagnostic: diagnostics){System.out.println("line:"+ diagnostic.getLineNumber());System.out.println("msg:"+ diagnostic.getMessage(Locale.ENGLISH));System.out.println("source:"+ diagnostic.getSource());}}private static JavaFileObject generateTest() {String contents = new String("package win.hgfdodo.dynamic;" +"class CalculatorTest {\n" +" public void testMultiply() {\n" +" Calculator c = new Calculator()\n" +" System.out.println(c.multiply(2, 4));\n" +" }\n" +" public static void main(String[] args) {\n" +" CalculatorTest ct = new CalculatorTest();\n" +" ct.testMultiply();\n" +" }\n" +"}\n");StringObject so = null;try {so = new StringObject("win.hgfdodo.dynamic.CalculatorTest", contents);} catch (URISyntaxException e) {e.printStackTrace();}return so;} }generateTest方法在構(gòu)造Calculator時,將行尾;去掉,造成java 源文件錯誤,在編譯時,會輸出:
line:3 msg:需要';' source:win.hgfdodo.dynamic.StringObject[win.hgfdodo.dynamic.CalculatorTest]運行時編譯和運行java類

CharSequenceJavaFileObject -- 存儲源代碼
package win.hgfdodo.compiler;import javax.tools.SimpleJavaFileObject; import java.io.IOException; import java.net.URI;/*** 字符串java源代碼。JavaFileObject表示*/ public class CharSequenceJavaFileObject extends SimpleJavaFileObject {//表示java源代碼private CharSequence content;protected CharSequenceJavaFileObject(String className, String content) {super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);this.content = content;}/*** 獲取需要編譯的源代碼* @param ignoreEncodingErrors* @return* @throws IOException*/@Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {return content;} }JavaClassObject 保存編譯結(jié)果
package win.hgfdodo.compiler;import javax.tools.SimpleJavaFileObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI;/*** 存儲編譯后的字節(jié)碼*/ public class JavaClassObject extends SimpleJavaFileObject {/*** Compiler編譯后的byte數(shù)據(jù)會存在這個ByteArrayOutputStream對象中,* 后面可以取出,加載到JVM中。*/private ByteArrayOutputStream byteArrayOutputStream;public JavaClassObject(String className, Kind kind) {super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);this.byteArrayOutputStream = new ByteArrayOutputStream();}/*** 覆蓋父類SimpleJavaFileObject的方法。* 該方法提供給編譯器結(jié)果輸出的OutputStream。* * 編譯器完成編譯后,會將編譯結(jié)果輸出到該 OutputStream 中,我們隨后需要使用它獲取編譯結(jié)果** @return* @throws IOException*/@Overridepublic OutputStream openOutputStream() throws IOException {return this.byteArrayOutputStream;}/*** FileManager會使用該方法獲取編譯后的byte,然后將類加載到JVM*/public byte[] getBytes() {return this.byteArrayOutputStream.toByteArray();} }JavaFileManager 處理編譯結(jié)果
JavaFileManager提供了編譯結(jié)果存儲和編譯類的加載。
package win.hgfdodo.compiler;import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import java.io.IOException; import java.security.SecureClassLoader;/*** 輸出字節(jié)碼到JavaClassFile*/ public class ClassFileManager extends ForwardingJavaFileManager {/*** 存儲編譯后的代碼數(shù)據(jù)*/private JavaClassObject classJavaFileObject;protected ClassFileManager(JavaFileManager fileManager) {super(fileManager);}/*** 編譯后加載類* <p>* 返回一個匿名的SecureClassLoader:* 加載由JavaCompiler編譯后,保存在ClassJavaFileObject中的byte數(shù)組。*/@Overridepublic ClassLoader getClassLoader(Location location) {return new SecureClassLoader() {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes = classJavaFileObject.getBytes();return super.defineClass(name, bytes, 0, bytes.length);}};}/*** 給編譯器提供JavaClassObject,編譯器會將編譯結(jié)果寫進去*/@Overridepublic JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {this.classJavaFileObject = new JavaClassObject(className, kind);return this.classJavaFileObject;}}DynamicCompiler -- 自定義編譯器
DynamicCompiler實現(xiàn)將源代碼編譯并加載的功能。
package win.hgfdodo.compiler;import javax.tools.*; import java.io.IOException; import java.util.ArrayList; import java.util.List;/*** 運行時編譯*/ public class DynamicCompiler {private JavaFileManager fileManager;public DynamicCompiler() {this.fileManager = initManger();}private JavaFileManager initManger() {if (fileManager != null) {return fileManager;} else {JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();DiagnosticCollector diagnosticCollector = new DiagnosticCollector();fileManager = new ClassFileManager(javaCompiler.getStandardFileManager(diagnosticCollector, null, null));return fileManager;}}/*** 編譯源碼并加載,獲取Class對象* @param fullName* @param sourceCode* @return* @throws ClassNotFoundException*/public Class compileAndLoad(String fullName, String sourceCode) throws ClassNotFoundException {JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();List<JavaFileObject> javaFileObjectList = new ArrayList<JavaFileObject>();javaFileObjectList.add(new CharSequenceJavaFileObject(fullName, sourceCode));boolean result = javaCompiler.getTask(null, fileManager, null, null, null, javaFileObjectList).call();if (result) {return this.fileManager.getClassLoader(null).loadClass(fullName);} else {return Class.forName(fullName);}}/*** 關(guān)閉fileManager* @throws IOException*/public void closeFileManager() throws IOException {this.fileManager.close();}}測試
package win.hgfdodo.compiler;import java.io.IOException; import java.lang.reflect.InvocationTargetException;public class DynamicCompilerTest {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {StringBuilder src = new StringBuilder();src.append("package win.hgfdodo.compiler;");src.append("public class DynaClass {\n");src.append(" public String toString() {\n");src.append(" return \"Hello, I am \" + ");src.append("this.getClass().getSimpleName();\n");src.append(" }\n");src.append("}\n");String fullName = "win.hgfdodo.compiler.DynaClass";DynamicCompiler compiler = new DynamicCompiler();Class clz = compiler.compileAndLoad(fullName, src.toString());System.out.println(clz.getConstructor().newInstance());compiler.close();} }編譯加載win.hgfdodo.compiler.DynaClass后,創(chuàng)建新的對象,并調(diào)用toString()輸出:
Hello, I am DynaClass參考
?
本文根據(jù)博客https://my.oschina.net/hgfdoing/blog/3052263
修改而來,注意, 編譯.java源碼, 和執(zhí)行.class 都需要制定文件夾,這里我使用了絕對路徑
2019年5月23日09:59:23
總結(jié)
以上是生活随笔為你收集整理的java Compiler API (java编译api)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PO采购-采购申请单
- 下一篇: 华为交换机测试吞吐量软件,【精选】ixc