Skip to content

Latest commit

 

History

History
157 lines (131 loc) · 6.71 KB

运行时编译.md

File metadata and controls

157 lines (131 loc) · 6.71 KB

技术前提

请完整阅读java使用命令编译打包文档后再继续阅读。

关键组件介绍:

  • JavaCompiler - 表示java编译器, run方法执行编译操作. 或者可以先生成编译任务(CompilationTask), 然后与调用任务的call方法执行
  • JavaFileObject - 表示一个java源文件对象
  • JavaFileManager - Java源文件管理类, 管理一系列JavaFileObject
  • Diagnostic - 表示一个诊断信息
  • DiagnosticListener - 诊断信息监听器, 编译过程触发. 生成编译task(JavaCompiler#getTask())或获取FileManager(JavaCompiler#getStandardFileManager())时需要传递DiagnosticListener以便收集诊断信息

编译本地源文件并输出到本地

读取本地的.java文件,动态编译为.class文件后保存在磁盘上。

第一种方式

public static void main(String[] args) {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    // 输入流、输出流、错误流、javac参数
    int result = compiler.run(null, null, null, "/path/to/source");
    if (result != 0) {
    System.out.println("编译失败");
    }
}

通过以上的方式,默认编译的文件和源文件存在同一目录下。可以通过-d参数指定编译输出位置。

第二种方式

public static void main(String[] args) {
    // 获取java编译器
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    // 获取文件管理器
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    // 定义要编译的源文件
    File file = new File("F:\\javalearning\\src\\com\\feb13th\\demo1\\HelloWorld.java");
    //通过源文件获取到要编译的Java类源码迭代器,包括所有内部类,其中每个类都是一个 JavaFileObject,也被称为一个汇编单元
    Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file);
    // options -d 指定编译后的文件位置, -classpath 指定依赖
    Iterable<String> options = Arrays.asList(
        "-d", "F:\\javalearning\\classes",
        "-classpath", "F:\\javalearning\\classes;F:\\javalearning\\lib\\fastjson-1.2.58.jar");
    // 生成编译任务
    JavaCompiler.CompilationTask task = compiler.getTask(null,
                                                         fileManager, 
                                                         null, 
                                                         options, 
                                                         null, 
                                                         compilationUnits);
    // 执行编译任务
    Boolean success = task.call();
    if (success) {
        System.out.println("编译成功");
    } else {
        System.out.println("编译失败");
    }
}

编译内存中的源码并写入内存

编译内存中的源码需要自定义JavaFileObject以及JavaFileManager。下面的例子中实现了读取内存中的java源码,并将编译后的结果字节码保存在map中。

如果只需要读取内存中的源码,将**(1)**修改为上一节中对应的代码即可。

如果只需要将编译后的字节码保存在内存中,将**(2)**修改为上一节对应的代码即可。

注: (1)(2)都有两处需要修改。

public static void main(String[] args) {
    // 获取java编译器
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    // 获取文件管理器
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    String className = "com.feb13th.HelloWorld";
    String code = "package com.feb13th;" +
        "public class HelloWorld { " +
        "   public static void main(String[] args) { " +
        "       System.out.println(\"HelloWorld!!\");" +
        "   }" +
        "}";
    // (1) 自定义JavaFileObject实现读取内存中的源文件
    JavaFileObject javaFileObject = new SimpleJavaFileObject(
        // 创建 uri, com/feb13th/HelloWorld.java
        URI.create(className.replaceAll("\\.", "/") + JavaFileObject.Kind.SOURCE.extension),
        JavaFileObject.Kind.SOURCE) {
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return code;
        }
    };

    // 自定义用于存储字节码的map
    Map<String, byte[]> bytes = new HashMap<>();
    // (2) 自定义JavaFileManager实现将编译后的字节码存储在内存中
    JavaFileManager javaFileManager = new ForwardingJavaFileManager<JavaFileManager>(fileManager) {
        @Override
        public JavaFileObject getJavaFileForOutput(Location location,
                                                   String className,
                                                   JavaFileObject.Kind kind,
                                                   FileObject sibling) throws IOException {
            if (kind == JavaFileObject.Kind.CLASS) {
                return new SimpleJavaFileObject(
                    URI.create(className + JavaFileObject.Kind.CLASS.extension),
                    JavaFileObject.Kind.CLASS) {
                    @Override
                    public OutputStream openOutputStream() throws IOException {
                        return new FilterOutputStream(new ByteArrayOutputStream()) {
                            @Override
                            public void close() throws IOException {
                                out.close();
                                ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                                bytes.put(className, bos.toByteArray());
                            }
                        };
                    }
                };
            }
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }
    };

    // 生成编译任务
    JavaCompiler.CompilationTask task = compiler.getTask(null,
                                                         javaFileManager, //(2)
                                                         null,
                                                         null, //(1)
                                                         null,
                                                         Arrays.asList(javaFileObject));
    // 执行编译任务
    Boolean success = task.call();
    if (success) {
        System.out.println("编译成功");
    } else {
        System.out.println("编译失败");
    }

    // 查看map中有没有存储字节码
    bytes.forEach((k, v) -> {
        System.out.println(k);
        System.out.println(v.length);
    });
}