JDK6開始提供了動態編譯的API,在許多應用場景都可以用得著,如動態加載(修改)服務、高性動態業務邏輯實現(用腳本或模板引擎實現效率滿足不了需求)等都非常好用。
API對應的接口都在javax.tools包下面,常用編譯方式有基於文本文件、內存字符串等,實際上基於URI的字節流都可以,也就是遠程Java源代碼也可以。對於常用的已有文件形式的動態編譯網上的實例已經非常多,我在這裡介紹下動態編譯內存中以字符串的形式。
簡單的代碼流程如下:
- //通過系統工具提供者獲得動態編譯器
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- //獲得一個文件管理器,它的功能主要是提供所有文件操作的規則,
- //如源代碼路徑、編譯的classpath,class文件目標目錄等,其相關屬性都提供默認值
- StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
- //獲得CompilationTask並調用
- //獲得CompilationTask方法原型:
- getTask(Writer out,
- JavaFileManager fileManager,
- DiagnosticListener super JavaFileObject> diagnosticListener,
- Iterable
options, - Iterable
classes, - Iterable extends JavaFileObject> compilationUnits)
- //簡單調用例子
- boolean b = jc.getTask(null, fileManager, null, null, null, compilationUnits).call();
- import Java.Net.URI;
- import javax.tools.SimpleJavaFileObject;
- public class JavaSourceFromString extends SimpleJavaFileObject {
- /**
- * 源碼
- */
- final String code;
- /**
- * 構造方法:從字符串中構造一個FileObject
- * @param name the name of the compilation unit represented by this file object
- * @param code the source code for the compilation unit represented by this file object
- */
- JavaSourceFromString(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;
- }
- }
完整的測試類:
Class TestDyCompile
- import Java.io.File;
- import Java.io.IOException;
- import Java.util.Arrays;
- import javax.tools.JavaCompiler;
- import javax.tools.JavaFileManager.Location;
- import javax.tools.JavaFileObject;
- import javax.tools.StandardJavaFileManager;
- import Javax.tools.StandardLocation;
- import Javax.tools.ToolProvider;
- import dyclass.Test;
- public class TestDyCompile {
- /**
- *
- * @author ZhangXiang
- * @param args
- * 2011-4-7
- */
- public static void main(String[] args) {
- StringBuilder classStr = new StringBuilder("package dyclass;public class Foo implements Test{");
- classStr.append("public void test(){");
- classStr.append("System.out.println(\"Foo2\");}}");
- JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
- StandardJavaFileManager fileManager = jc.getStandardFileManager(null, null, null);
- Location location = StandardLocation.CLASS_OUTPUT;
- File[] outputs = new File[]{new File("bin/")};
- try {
- fileManager.setLocation(location, Arrays.asList(outputs));
- } catch (IOException e) {
- e.printStackTrace();
- }
- JavaFileObject jfo = new JavaSourceFromString("dyclass.Foo", classStr.toString());
- JavaFileObject[] jfos = new JavaFileObject[]{jfo};
- Iterable extends JavaFileObject> compilationUnits = Arrays.asList(jfos);
- boolean b = jc.getTask(null, fileManager, null, null, null, compilationUnits).call();
- if(b){//如果編譯成功
- try {
- Test t = (Test) Class.forName("dyclass.Foo").newInstance();
- t.test();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- }
我在這裡的具體業務類為dyclass.Foo,也就是我們需要動態編譯的類,為了方便寫業務的調用代碼,也可以讓我們的業務類實現一個接口,然後通過反射獲得具體子類強制轉換來調用。
Test接口:
- public interface Test {
- //業務方法簽名
- void test();
- }
另外,在代碼中還有這麼一段:
- Location location = StandardLocation.CLASS_OUTPUT;
- File[] outputs = new File[]{new File("bin/")};
- try {
- fileManager.setLocation(location, Arrays.asList(outputs));
- } catch (IOException e) {
- e.printStackTrace();
- }