Java Compiler 应用实例

本文介绍如何使用 Java Compiler API 实现动态编译与运行 Java 源文件,包括 run 方法与 getTask 方法的使用,以及通过反射调用生成类中的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一直在用JDK1.5, 一直搞不清楚JDK1.6有啥特性, 就翻了翻, 发现这个Compiler API(JSR 199)动态编译Java源文件功能很有意思. Compiler API如果和反射功能一起使用, 就可以实现java源代码的动态编译并执行这些代码,有点动态语言的特征. 利用这些API普通用户也可以方便的开发自己的编译器,动态生成代码,编译并运行. 本文就通过一个动态编译并运行源文件的例子简单说明下Compile API的基本功能, 有兴趣的可以深入研究下. 完整的代码工程从这里下载: http://dl.iteye.com/topics/download/0807c557-4f0d-3aba-956f-9fe5c9b83962

实例中实现的功能描述:

1. 使用JavaCompiler对象的run方法编译java源代码,并在源代码所在目录生成对应的class文件

2. 使用JavaCompiler对象的getTask方法编译java源代码,并将对应的class文件生成到指定目录, 并执行所生成类中指定的"printClassName"方法

环境准备:

首先回顾一下JDK, JRE,JVM的概念和关系:

JRE是java的运行环境, 说白了有JRE才能运行java类; 同时java类是运行于虚拟机(JVM)上的, 其实虚拟机是JRE的一部分, 具体来讲,在windows上就是JRE下面的一个JVM.dll文件; JDK就是java开发工具箱, 具有编译java类的功能和运行java类的功能(自身包含了一个JRE).

知道了JDK,JRE,JVM的关系,我们就应该明白,如果要在eclipse里面使用java的编译功能必须在eclipse里面使用JDK作为Library,否则在eclipse中获取不了JavaCompiler的对象. 设置如下图:

懒得找JDK1.6,我就直接下载了个1.7装了下,然后开发工具使用MyEclipse (当然用的是免费版的 -:)).


在看我们的实例分析及源码:

首先看下run方法编译java源文件, run方法比较简单,但不能指定输出路径,监控错误信息, 调用后就在源码所在目录生成class文件,run方法的声明如下:

int run(InputStream in,
        OutputStream out,
        OutputStream err,
        String... arguments)使用给定 I/O 通道和参数运行工具。按照惯例,工具如果运行成功,则返回 0;如果出现错误,则返回非 0 值。任何生成的诊断都将以某种未指定的格式写入 out 或 err。 

参数:
in - “标准”输入;如果为 null,则使用 System.in
out - “标准”输出;如果为 null,则使用 System.out
err - “标准”错误;如果为 null,则使用 System.err
arguments - 要传递给工具的参数 
返回:
如果成功,则返回 0;否则返回非 0 值 
抛出: 
NullPointerException - 如果参数数组包含任何 null 元素。

实例源码,注释比较详细,不再解释,Compiler.java中代码片段:

/**
     * Author: Jiangtao He; Email: ross.jiangtao.he@gmail.com
     * @param sFullFileName: the java source file name with full path
     * @return bRet: true-compile successfully, false - compile unsuccessfully
     * Description: Compile java source file to java class with run method
     */
    public boolean compileFile(String sFullFileName)
    {
        boolean bRet = false;
        // get compiler
        JavaCompiler oJavaCompiler = ToolProvider.getSystemJavaCompiler();
        // compile the java source code by run method
        int iCompileRet = oJavaCompiler.run(null, null, null, sFullFileName);
        // set compile result
        if (0 == iCompileRet)
        {
            bRet = true;
        }
        return bRet;
    }

再看下我们的getTask方法编译java源代码, 这个方法其实是构造了一个JavaCompiler.CompilationTask对象, 然后在调用这个对象的call方法, 在构造对象的过程中, 可以指定class的生成路径,监控错误信息,调用过程如下:

1) 生成JavaCompiler对象,用于构造CompilationTask对象,并编译java源代码

2) 构造DiagnosticCollector对象,用于存储诊断信息

3) 构造oStandardJavaFileManager对象,用于设置类的生成路径, 为了方便使用java反射方法,我直接将本实例中的输出路径设置为工程bin目录.实际应用中应根据场景生成道不同的目录--比如可以根据配置或者包名来做.

4) 生成源文件迭代器Iterable对象, 用于存储java源代码文件完整的路径

5) 根据上面生成的对象, 调用JavaCompiler对象的getTask构造CompilationTask对象, 并调用其call方法,编译源代码

再看下getTask方法的声明:

JavaCompiler.CompilationTask getTask(Writer out,
                                     JavaFileManager fileManager,
                                     DiagnosticListener<? super JavaFileObject> diagnosticListener,
                                     Iterable<String> options,
                                     Iterable<String> classes,
                                     Iterable<? extends JavaFileObject> compilationUnits)使用给定组件和参数创建编译任务的 future。该编译可能没有完成,正如 CompilationTask 接口中所述。 
如果提供了文件管理器,则它必须能够处理 StandardLocation 中定义的所有位置。 


参数:
out - 用于来自编译器的其他输出的 Writer;如果为 null,则使用 System.err
fileManager - 文件管理器;如果为 null,则使用编译器的标准文件管理器
diagnosticListener - 诊断侦听器;如果为 null,则使用编译器的默认方法报告诊断信息
options - 编译器选项;null 表示没有选项
classes - 类名称(用于注释处理),null 表示没有类名称
compilationUnits - 要编译的编译单元;null 表示没有编译单元 
返回:
表示编译的对象 
抛出: 
RuntimeException - 如果在用户提供的组件中发生不可恢复的错误。cause 为用户代码中的错误。 
IllegalArgumentException - 如果给定的任一编译单元具有不同于 source 的类型

源码清单如下,Compiler.java中代码片段:

/**
     * Author: Jiangtao He; Email: ross.jiangtao.he@gmail.com
     * @param sFullFileName: the java source file name with full path
     * @param sOutputPath: the output path of java class file
     * @return bRet: true-compile successfully, false - compile unsuccessfully
     * Description: Compile java source file to java class with getTask
     *     method, it can specify the class output path and catch diagnostic
     *     information
     * @throws IOException 
     */
    public boolean compileFile(String sFullFileName, String sOutputPath) throws IOException
    {
        boolean bRet = false;
        // get compiler
        JavaCompiler oJavaCompiler = ToolProvider.getSystemJavaCompiler();

        // define the diagnostic object, which will be used to save the
        // diagnostic information
        DiagnosticCollector<JavaFileObject> oDiagnosticCollector = new DiagnosticCollector<JavaFileObject>();

        // get StandardJavaFileManager object, and set the diagnostic for the
        // object
        StandardJavaFileManager oStandardJavaFileManager = oJavaCompiler
                .getStandardFileManager(oDiagnosticCollector, null, null);

        // set class output location
        Location oLocation = StandardLocation.CLASS_OUTPUT;
        try
        {
            oStandardJavaFileManager.setLocation(oLocation, Arrays
                    .asList(new File[] { new File(sOutputPath) }));

            // get JavaFileObject object, it will specify the java source file.
            Iterable<? extends JavaFileObject> oItJavaFileObject = oStandardJavaFileManager
                    .getJavaFileObjectsFromFiles(Arrays.asList(new File(
                            sFullFileName)));

            // compile the java source code by using CompilationTask's call
            // method
            bRet = oJavaCompiler.getTask(null, oStandardJavaFileManager,
                    oDiagnosticCollector, null, null, oItJavaFileObject).call();

            //print the Diagnostic's information
            for (Diagnostic oDiagnostic : oDiagnosticCollector
                    .getDiagnostics())
            {
                System.out.println("Error on line: "
                        + oDiagnostic.getLineNumber() + "; URI: "
                        + oDiagnostic.getSource().toString());
            }
        }
        catch (IOException e)
        {
            //exception process
            System.out.println("IO Exception: " + e);
            throw e;
        }
        finally
        {
            //close file manager
            if (null != oStandardJavaFileManager)
            {
                oStandardJavaFileManager.close();
            }
        }
        return bRet;
    }

编译的方法就这两个简单吧, 下面我们测试下这两个方法:

首先, 声明下我们的compiler类的对象,初始化下编译的类和输出类的路径,MyMain.java中代码片段:

        // get compiler object
        Compiler oCompiler = new Compiler();
        // the java source file name with full path
        String sFullFileName = "E:\\myspace\\CompilerSample\\Sample.java";
        // define the output path of java class, since this demo is ran into
        // eclipse, so set it as bin
        String sOutputPath = "bin/";

测试run方法:

       // Compile java source file to java class with run method
        boolean bRet = oCompiler.compileFile(sFullFileName);
        // print result
        if (bRet)
        {
            System.out.println("Compile the source code \"" + sFullFileName
                    + "\" successfully");
        }
        else
        {
            System.out.println("Compile the source code \"" + sFullFileName
                    + "\" unsuccessfully");
        }
run方法测试,控制台信息:

Compile the source code "E:\myspace\CompilerSample\Sample.java" successfully

生成的类文件抓图:


测试getTask方法,并利用java反射运行所生成类中的"printClassName"方法:

// Compile java source file, and output the class file into specified
        // path
        bRet = oCompiler.compileFile(sFullFileName, sOutputPath);
        // print result
        if (bRet)
        {
            System.out.println("Compile the source code \"" + sFullFileName
                    + "\" successfully");
            // if compile success, then execute the printClassName method of the
            // compiled class
            System.out
                    .println("Execute the printClassName method of the compiled class: ");
            System.out.print("  ");
            // load the class
            Class oClass = Class.forName("Sample");
            // new an object of sample class
            Object oObject = oClass.newInstance();
            // get object of printClassName method
            Method oMethod = oClass.getMethod("printClassName");
            oMethod.invoke(oObject);
        }
        else
        {
            System.out.println("Compile the source code \"" + sFullFileName
                    + "\" unsuccessfully");
        }
    }

运行测试方法后,控制台打印信息:

Compile the source code "E:\myspace\CompilerSample\Sample.java" successfully
Execute the printClassName method of the compiled class: 
  Print the class name: Sample

生成的类文件抓图:


至此, 通过java Compiler API动态编译并运行源文件的例子就完了.

注: 转载请注明出处: http://hejiangtao.iteye.com/ 用于商业得给我分成大笑

<think>嗯,用户之前问过关于“无效的目标发行版:17”的问题,现在又提到“Java Compiler”,需要介绍一下Java编译器。首先,我得确定用户的需求是什么。他们可能遇到了编译问题,想深入了解Java编译器的工作原理或者如何配置。 可能用户在使用Maven或IDE时遇到了编译错误,尤其是版本不匹配的问题,所以想了解编译器的作用和如何调整设置。之前的回答里提到了Maven编译器插件,所以这次需要更详细地解释Java编译器本身,包括不同实现、命令行使用,以及如何与构建工具集成。 用户可能不太清楚javac和ECJ的区别,或者为什么IDE有时候会报不同的错误。需要分清楚不同编译器的特点,比如OpenJDK的javac和Eclipse的ECJ,以及它们在处理代码时的差异。 另外,用户可能对如何在实际项目中配置编译器选项感兴趣,比如设置源代码版本、目标版本,处理注解处理器等。需要提供具体的配置示例,比如在Maven中如何指定编译器版本,或者在IDE中调整设置。 还要注意用户可能的实际应用场景,比如多模块项目中的编译器配置,或者如何处理常见的编译错误。需要给出实际案例,比如如何解决版本不一致导致的错误,或者如何处理依赖问题。 最后,需要确保回答结构清晰,分点说明,包括基本概念、编译器类型、工作流程、常用命令、配置方法以及常见问题处理。这样用户能逐步理解Java编译器的作用和使用方法,解决他们实际开发中遇到的问题。</think>### **Java编译器核心解析** --- #### **1. 编译器本质作用** Java编译器(`javac`)是将 **`.java`源代码** 转换为 **`.class`字节码** 的核心工具,关键转换过程: $$ \text{Java源码} \xrightarrow{\text{词法分析 → 语法分析 → 语义分析 → 字节码生成}} \text{JVM可执行字节码} $$ --- #### **2. 主流编译器类型** | 编译器类型 | 实现厂商 | 特点 | |------------------|------------|----------------------------------------------------------------------| | **javac** | Oracle | OpenJDK官方编译器,严格遵循语言规范 | | **ECJ** | Eclipse | 增量编译速度快,内存占用低,用于Eclipse IDE | | **Groovy-Eclipse**| Groovy | 支持动态语言特性编译 | --- #### **3. 编译器核心工作流程** ```mermaid graph LR A[Source Code] --> B[词法分析] B --> C[生成Token流] C --> D[语法分析] D --> E[生成AST抽象语法树] E --> F[语义分析] F --> G[符号表填充] G --> H[字节码生成] H --> I[Class文件] ``` --- #### **4. 关键命令行参数** ```bash # 基础编译 javac -source 17 -target 17 -encoding UTF-8 Main.java # 高级参数 javac -parameters # 保留方法参数名(反射可用) javac -Xlint:unchecked # 显示未检查的类型转换警告 javac -cp lib/*.jar # 指定类路径 ``` --- #### **5. IDE与构建工具集成** - **Maven配置示例**: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <release>17</release> <!-- 替代source/target --> <compilerArgs> <arg>-Xlint:all</arg> </compilerArgs> </configuration> </plugin> ``` - **Gradle配置**: ```groovy tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' sourceCompatibility = '17' targetCompatibility = '17' options.compilerArgs += ['-parameters'] } ``` --- #### **6. 常见编译错误处理** 1. **不兼容的类型** ```java // 错误示例 List numbers = new ArrayList<Integer>(); numbers.add("text"); // 触发编译错误 ``` **解决方案**:使用泛型声明 `List<Integer> numbers = new ArrayList<>()` 2. **未报告的异常** ```java // 错误示例 FileInputStream fis = new FileInputStream("file.txt"); ``` **解决方案**:添加异常处理 `try-catch` 或声明 `throws` 3. **模块系统冲突** ```bash error: module A reads package B which is in both module C and module D ``` **解决方案**:使用 `--patch-module` 或重构模块依赖 --- #### **7. 高级编译技术** 1. **注解处理器(Annotation Processing)** ```java // 自定义处理器示例 @SupportedAnnotationTypes("com.example.GenerateCode") @SupportedSourceVersion(SourceVersion.RELEASE_17) public class CodeGenerator extends AbstractProcessor { @Override public boolean process(...) { // 生成新代码 } } ``` 2. **编译器优化策略** ```bash javac -O # 启用基本优化 javoc -XDoptimize=2 # 启用高级优化(特定实现) ``` --- #### **8. 跨版本编译策略** | 场景 | 解决方案 | |------------------------|--------------------------------------------------------------------------| | 使用JDK17编译JDK8代码 | `javac -source 8 -target 8 -bootclasspath jdk8/lib/rt.jar` | | 向后兼容性验证 | 使用`Animal Sniffer Maven Plugin`验证API兼容性 | --- #### **9. 编译器诊断工具** 1. **反编译验证**: ```bash javap -c -v MyClass.class # 查看字节码 ``` 2. **编译过程调试**: ```bash javac -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 ``` --- ### **最佳实践建议** 1. **版本控制**:始终在构建配置中明确指定`<release>`参数 2. **持续集成**:在CI环境中固定JDK版本(推荐使用Docker镜像) 3. **编译器日志**:启用详细日志分析编译问题: ```bash mvn clean compile -X | grep -i 'compiler' ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值