java中的CompileAPI入門及使用


介紹

java5之前我們可以通過java提供的tools.jar來操作java編譯器,java6提供了新的API,讓我們可以更方便的調用。包名為javax.tools。

使用

通過文件編譯

String filePath = "D:\\Client.java";
//獲取java編譯器
    JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
//編譯
    int result = javaCompiler.run(null, null, null, filePath);
    System.out.println(result);

結果為0表示編譯成功,在相同目錄下生成了Client.class文件。
編譯參數依次為

  1. java編譯器提供參數,如果為null,以System.in代替
  2. 得到Java編譯器的輸出信息,如果為null,以System.out代替
  3. 接收編譯器的錯誤信息,如果為null,以System.err代替
  4. 一個或多個Java源程式文件

通過非文件格式編譯

java還提供了編譯其他形式的源文件的功能,如內存字符串文本,數據庫讀取的文本。

public class JavaFileManagerMain {
  public static void main(String[] args) {
//文件路徑
    String fullQuanlifiedFileName = "D:\\Client.java";
//獲取編譯器
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//獲取文件管理器 參數依次為錯誤監聽器,區域對象,編碼
    StandardJavaFileManager fileManager =
        compiler.getStandardFileManager(null, null, null);
//通過文件全路徑獲取要編譯的文件對象
    Iterable<? extends JavaFileObject> files =
        fileManager.getJavaFileObjectsFromStrings(
            Arrays.asList(fullQuanlifiedFileName));
//創建編譯任務 參數為錯誤輸出流,文件管理器,錯誤處理器,編譯器選項,參與編譯的class,帶編譯的java文件
    JavaCompiler.CompilationTask task = compiler.getTask(
        null, fileManager, null, null, null, files);
//執行任務
    Boolean result = task.call();
    if (result) {
      System.out.println("Succeeded");
    }
  }
}

接下來實現從內存中讀取待編譯對象

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;
  }

  @Override
  public CharSequence getCharContent(boolean ignoreEncodingErrors) {
    return content;
  }

}
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 = "package com.imooc.sourcecode.java.javacompile.test3;" +
        "class Test {\n" +
        "  public static void main(String[] args) {\n" +
        "    System.out.println(\"success\");\n" +
        "  }\n" +
        "}\n";
    StringObject so = null;
    try {
      so = new StringObject("com.imooc.sourcecode.java.javacompile.test3.Test", contents);
    } catch (URISyntaxException e) {
      e.printStackTrace();
    }
    return so;
  }
}

結果編譯成功。

實現在運行期編譯及加載類

定義源代碼存儲類

/**
 * 待編譯對象 存儲待編譯的字符串
 */
public class JavaSourceFileObject extends SimpleJavaFileObject {

  //表示java源代碼
  private CharSequence content;

  protected JavaSourceFileObject(String className, String content) {
    super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
    this.content = content;
  }

  /**
   * 獲取需要編譯的源代碼
   *
   * @param ignoreEncodingErrors
   * @return
   * @throws IOException
   */
  @Override
  public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
    return content;
  }
}

定義編譯結果存儲類

/**
 * 存儲編譯之后的class內容
 */
public class JavaTargetFileObject extends SimpleJavaFileObject {

  /**
   * Compiler編譯后的byte數據會存在這個ByteArrayOutputStream對象中,
   * 后面可以取出,加載到JVM中。
   */
  private ByteArrayOutputStream byteArrayOutputStream;

  public JavaTargetFileObject(String className, Kind kind) {
    super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
    this.byteArrayOutputStream = new ByteArrayOutputStream();
  }

  /**
   * 覆蓋父類SimpleJavaFileObject的方法。
   * 該方法提供給編譯器結果輸出的OutputStream。
   * <p>
   * 編譯器完成編譯后,會將編譯結果輸出到該 OutputStream 中,我們隨后需要使用它獲取編譯結果
   *
   * @return
   * @throws IOException
   */
  @Override
  public OutputStream openOutputStream() throws IOException {
    return this.byteArrayOutputStream;
  }

  /**
   * FileManager會使用該方法獲取編譯后的byte,然后將類加載到JVM
   */
  public byte[] getBytes() {
    return this.byteArrayOutputStream.toByteArray();
  }
}

定義自己的文件管理器

/**
 * 內存文件管理器
 * @see  JavaTargetFileObject
 */
public class ClassFileManager extends ForwardingJavaFileManager {

  /**
   * 存儲編譯后的代碼數據
   */
  private JavaTargetFileObject classJavaFileObject;

  protected ClassFileManager(JavaFileManager fileManager) {
    super(fileManager);
  }

  /**
   * 編譯后加載類
   * <p>
   * 返回一個匿名的SecureClassLoader:
   * 加載由JavaCompiler編譯后,保存在ClassJavaFileObject中的byte數組。
   */
  @Override
  public ClassLoader getClassLoader(Location location) {
    return new SecureClassLoader() {
      @Override
      protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = classJavaFileObject.getBytes();
        return super.defineClass(name, bytes, 0, bytes.length);
      }
    };
  }

  /**
   * 給編譯器提供JavaClassObject,編譯器會將編譯結果寫進去
   */
  @Override
  public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
      throws IOException {
    this.classJavaFileObject = new JavaTargetFileObject(className, kind);
    return this.classJavaFileObject;
  }

}

定義一個實現類編譯和加載

/**
 * 運行時編譯
 */
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<JavaFileObject> 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<>();
    javaFileObjectList.add(new JavaSourceFileObject(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);
    }
  }

  /**
   * 關閉fileManager
   *
   * @throws IOException
   */
  public void close() throws IOException {
    this.fileManager.close();
  }

}

參考jdk11中的JShell實現,核心類為TaskFactory和MemoryFileManager。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM