本文章主要介绍了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。


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

java调用javascript知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。