jdk1.6中添加了編譯API, 我們可以在java代碼中調用編譯API動態(tài)編譯JAVA源文件, 也就是運行時編譯. 下面對JAVA動態(tài)編譯API的用法進行簡單介紹:
一. 編譯API概要
下面是動態(tài)編譯API相關類的類圖:
編譯API.png
關鍵組件介紹:
- JavaCompiler - 表示java編譯器, run方法執(zhí)行編譯操作. 還有一種編譯方式是先生成編譯任務(CompilationTask), 讓后調用CompilationTask的call方法執(zhí)行編譯任務
- JavaFileObject - 表示一個java源文件對象
- JavaFileManager - Java源文件管理類, 管理一系列JavaFileObject
- Diagnostic - 表示一個診斷信息
- DiagnosticListener - 診斷信息監(jiān)聽器, 編譯過程觸發(fā). 生成編譯task(
JavaCompiler#getTask()
)或獲取FileManager(JavaCompiler#getStandardFileManager()
)時需要傳遞DiagnosticListener以便收集診斷信息
二. 用法
- (1) 下面的demo, 先構建一個類的源文件, 再使用流寫入到磁盤中, 接著調用編譯api, 編譯磁盤上的源文件, 最后使用反射調加載并調用編譯后的字節(jié)碼. 代碼如下:
package com.example.dynamic_compiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class DynamicCompileDemo {
static void eval(String source) {
//構建一個類的源代碼
StringBuffer sourceCode = new StringBuffer();
sourceCode.append("public class Temp {").append("\r\n")
.append("public static String call(String args) {").append("\r\n")
.append("System.out.println(\""+source+"\");").append("\r\n")
.append("return \"Hello, " + source + "\";").append("\r\n")
.append("}").append("\r\n")
.append("}");
try {
//將源文件寫入到磁盤中
String javaFileName = "Temp.java";
//生成的Java源文件存放到<module>/build/generated/source/java目錄下 (開發(fā)工具為Android Studio, java-demo是我的module名稱)
File sourceDir = new File("java-demo/build/generated/source/java");
if (!sourceDir.exists()) {
sourceDir.mkdirs();
}
File javaFile = new File(sourceDir, javaFileName);
PrintWriter writer = new PrintWriter(new FileWriter(javaFile));
writer.write(sourceCode.toString());
writer.flush();
writer.close();
//動態(tài)編譯磁盤中的代碼
//生成的字節(jié)碼文件存放到<module>/build/classes/main目錄下
File distDir = new File("java-demo/build/classes/main");
if (!distDir.exists()) {
distDir.mkdirs();
}
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
//JavaCompiler最核心的方法是run, 通過這個方法編譯java源文件, 前3個參數(shù)傳null時,
//分別使用標準輸入/輸出/錯誤流來 處理輸入和編譯輸出. 使用編譯參數(shù)-d指定字節(jié)碼輸出目錄.
int compileResult = javac.run(null, null, null, "-d", distDir.getAbsolutePath(), javaFile.getAbsolutePath());
//run方法的返回值: 0-表示編譯成功, 否則表示編譯失敗
if(compileResult != 0) {
System.err.println("編譯失敗!!");
return;
}
//動態(tài)執(zhí)行 (反射執(zhí)行)
Class klass = Class.forName("Temp");
Method evalMethod = klass.getMethod("call", String.class);
String result = (String)evalMethod.invoke(klass.newInstance(), source);
System.out.println("eval(" + source + ") = " + result);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
eval("Lucy");
}
}
運行后輸出結果如下:
運行結果
- (2) 下面的demo, 使用編譯api編譯內存中的java源代碼. 代碼如下:
a. 自定義一個JavaFileObject對象, 用于表示Java源代碼對象, 如下:
package com.example.dynamic_compiler;
import java.net.URI;
import javax.tools.SimpleJavaFileObject;
public class JavaFileObjectFromString extends SimpleJavaFileObject {
//Uri uri; //uri為源碼的資源定位符, 在父類中定義
/**
* Java源文件的內容
*/
final String code;
/**
* @param className 類的完整名稱
* @param code
*/
public JavaFileObjectFromString(String className, String code) {
//uri為類的資源定位符號, 如: com/stone/generate/Hello.java
super(URI.create(className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
//uri值為com.stone.generate.Hello時, 下面注釋的代碼導致異常 => com.stone.generate.Hello:1: 錯誤: 類Hello是公共的, 應在名為 Hello.java 的文件中聲明
// super(URI.create(className), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
(b) 使用一個類的源碼字符串構造一個JavaFileObject, 讓后調用編譯API編譯此JavaFileObject對象, 并生成字節(jié)碼, 最后使用放射調用編譯結果. 代碼如下:
package com.example.dynamic_compiler;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
public class Demo1 {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//構建java源文件
StringBuilder sourceCode = new StringBuilder();
sourceCode.append("package com.stone.generate;")
.append("public class Hello {")
.append("public static void main(String[] args) {")
.append("System.out.println(\"Hello World !\");")
.append("}")
.append("}");
// //將代碼寫入到內存
// StringWriter writer = new StringWriter(); //寫入到內存中
// PrintWriter out = new PrintWriter(writer);
// out.println(sourceCode.toString());
// out.flush();
// out.close();
//動態(tài)編譯內存中的代碼
File distDir = new File("java-demo/build/classes/main");
if (!distDir.exists()) {
distDir.mkdirs();
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// JavaFileObject javaFileObject = new JavaFileObjectFromString("Hello", writer.toString());
JavaFileObject javaFileObject = new JavaFileObjectFromString("com.stone.generate.Hello", sourceCode.toString());
JavaCompiler.CompilationTask task = compiler.getTask(null, null, null,
Arrays.asList("-d", distDir.getAbsolutePath()), null,
Arrays.asList(javaFileObject));
boolean compileSuccess = task.call();
if (!compileSuccess) {
System.out.println("編譯失敗");
} else {
//動態(tài)執(zhí)行 (反射執(zhí)行)
System.out.println("編譯成功");
URL[] urls = new URL[] {new URL("file:/" + distDir.getAbsolutePath())};
URLClassLoader classLoader = new URLClassLoader(urls);
Class dynamicClass = classLoader.loadClass("com.stone.generate.Hello");
Method method = dynamicClass.getDeclaredMethod("main", String[].class);
String[] arguments = {null};
method.invoke(dynamicClass, arguments);
}
}
}
運行后輸出結果如下:
運行結果
- (3) 編譯編譯一個錯誤的Java類. 使用診斷信息監(jiān)聽器手機診斷信息. 代碼如下:
package com.example.dynamic_compiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* @author stone
* @date 16/9/6
*/
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//構建java源文件
String javaFileName = "Demo.java";
StringBuilder sourceCode = new StringBuilder();
//正常的java源代碼
// sourceCode.append("package com.stone.generate;").append("\r\n")
// .append("public class Demo {").append("\r\n")
// .append("public static void main(String[] args) {").append("\r\n")
// .append("System.out.println(\"Hello World !\");").append("\r\n")
// .append("}").append("\r\n")
// .append("}");
//錯誤的java源代碼
sourceCode.append("package com.stone.generate;").append("\r\n\n")
.append("public class Demo ").append("\r\n") //此處少了一個: `{`
.append("public static void main(String[] args) {").append("\r\n")
.append("System.out.println(\"Hello World !\");").append("\r\n")
.append("}").append("\r\n")
.append("}");
//見文件寫到磁盤
File sourceDir = new File("java-demo/build/generated/source/java");
if (!sourceDir.exists()) {
sourceDir.mkdirs();
}
File javaFile = new File(sourceDir, javaFileName);
PrintWriter out = new PrintWriter(new FileWriter(javaFile));
out.write(sourceCode.toString());
out.flush();
out.close();
//獲取編譯器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//創(chuàng)建診斷信息監(jiān)聽器, 用于手機診斷信息
DiagnosticCollector<JavaFileObject> diagnosticListeners = new DiagnosticCollector<>();
//獲取FileManager
StandardJavaFileManager javaFileManager = compiler.getStandardFileManager(diagnosticListeners, null, null);
Iterable it = javaFileManager.getJavaFileObjects(javaFile);
File distDir = new File("java-demo/build/classes/main");
if (!distDir.exists()) {
distDir.mkdir();
}
//生編譯任務
JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticListeners,
Arrays.asList("-d", distDir.getAbsolutePath()), null, it);
//執(zhí)行編譯任務
task.call();
//輸出診斷信息
for(Diagnostic<? extends JavaFileObject> diagnostic : diagnosticListeners.getDiagnostics()) {
//可以在此處自定義編譯診(錯誤)斷信息的輸出格式
System.out.format("Error on line %d in %s%n",
diagnostic.getLineNumber(),
diagnostic.getSource().toUri());
}
//關閉FileManager
javaFileManager.close();
//動態(tài)執(zhí)行
Class klass = Class.forName("com.stone.generate.Demo");
Method method = klass.getDeclaredMethod("main", String[].class);
method.invoke(klass, new String[]{null});
}
}
運行結果如下:
運行結果