一、背景与定位
1. 诞生背景
在 Java 6(JSR 199) 之前,Java 的编译能力主要以两种形式存在:
- 命令行工具 :
javac - IDE / 构建工具内部实现(如 Ant、IDEA、Eclipse 的编译器)
这些方式存在明显局限:
- 无法在 运行时 直接进行 Java 源码编译
- 无法在 服务端 / 工具链 / DSL / 插件系统 中嵌入标准编译能力
- 缺乏统一、标准、可扩展的编译 API
Java Compiler API(JSR 199) 的目标是:
将
javac的核心能力以 标准化、可编程 API 的形式暴露出来。
二、整体架构与核心组件
Java Compiler API 主要位于:
javax.tools
其整体结构可以理解为一个 可嵌入式编译器框架。
![[1_1Java Compiler API.png]]
![[1_2Java Compiler API.png]]
1. 核心接口总览
| 接口 / 类 | 作用 |
|---|---|
JavaCompiler |
编译器主入口 |
ToolProvider |
获取系统编译器 |
CompilationTask |
一次编译任务 |
JavaFileObject |
抽象的"源码/字节码文件" |
JavaFileManager |
文件管理与输出控制 |
StandardJavaFileManager |
默认文件系统实现 |
Diagnostic / DiagnosticCollector |
编译错误、警告信息 |
FileObject |
更通用的文件抽象 |
三、核心接口详解
1. ToolProvider & JavaCompiler
java
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
关键说明
-
仅在 JDK 中可用
-
如果运行环境是 JRE / jlink runtime ,该方法返回
null -
本质上获取的是
javac的程序化入口
2. JavaFileObject:源码与字节码的统一抽象
java
public interface JavaFileObject extends FileObject
它抽象了:
.java源码.class字节码- 内存中的"虚拟文件"
常见实现
SimpleJavaFileObjectStandardJavaFileManager内部实现
这也是 "内存编译""动态代码生成" 的基础。
3. JavaFileManager:编译输入 / 输出控制中心
负责:
- 源文件的查找
- classpath / module-path 的解析
- 编译产物(
.class)的输出位置
java
StandardJavaFileManager fm =
compiler.getStandardFileManager(diagnostics, null, null);
高级用法
- 自定义
JavaFileManager - 将
.class输出到:- 内存
- 数据库
- 网络
- 自定义 ClassLoader
4. CompilationTask:一次完整的编译任务
java
JavaCompiler.CompilationTask task =
compiler.getTask(
writer,
fileManager,
diagnostics,
options,
classes,
compilationUnits
);
参数语义
| 参数 | 含义 |
|---|---|
writer |
编译器输出(stdout/stderr) |
fileManager |
文件管理 |
diagnostics |
错误收集 |
options |
-classpath、-source 等 |
classes |
注解处理目标类 |
compilationUnits |
待编译源码 |
5. Diagnostic:编译错误的结构化表示
java
for (Diagnostic<?> d : diagnostics.getDiagnostics()) {
System.out.println(d.getKind());
System.out.println(d.getMessage(null));
}
可获取:
- 错误 / 警告 / 提示
- 行号、列号
- 源文件
- 错误码
这使得 IDE、代码分析器、在线判题系统 成为可能。
四、最小可运行示例
1. 编译磁盘上的 Java 文件
java
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> units =
fileManager.getJavaFileObjectsFromStrings(
List.of("HelloWorld.java"));
boolean success = compiler.getTask(
null,
fileManager,
diagnostics,
null,
null,
units
).call();
2. 内存编译(无文件系统)
核心思路:
- 自定义
SimpleJavaFileObject - 用
String存源码 - 用
ByteArrayOutputStream存.class
这是 脚本引擎 / DSL / 规则引擎 的基础能力。
五、与 Annotation Processing(JSR 269)的关系
Java Compiler API 与 注解处理器 深度集成:
text
javac
├─ Parse
├─ Enter
├─ Annotation Processing ← APT
├─ Attr
├─ Flow
├─ Desugar
└─ Generate
典型应用
- Lombok
- MapStruct
- AutoValue
- Dagger
- 自研代码生成器
你可以通过 Compiler API 显式启用 / 控制注解处理器。
六、典型应用场景
1. 代码分析 / 静态检查
- 自研 Sonar 规则引擎
- 编译级 AST 校验
- API 兼容性检查
2. 动态代码执行
- 在线判题系统
- 规则引擎
- 表达式语言(EL / DSL)
3. 插件系统
- 热加载业务逻辑
- 用户自定义扩展
- 脚本替代 Groovy / JS
4. IDE / 工具链
- 语法检查
- 实时错误提示
- 编译日志可视化
七、与 Roslyn(C#)的对比
| 维度 | Java Compiler API | Roslyn |
|---|---|---|
| AST 可操作性 | 中等 | 极强 |
| API 现代性 | 一般 | 非常好 |
| 编译即服务 | 支持 | 原生支持 |
| 增量编译 | 弱 | 强 |
| 语法树改写 | 困难 | 一等公民 |
Java Compiler API 偏"编译控制"
Roslyn 偏"编译平台"
八、局限性与注意事项
- 不是官方 Script Engine
- API 偏底层,使用成本高
- AST 操作不如 Roslyn 直观
- 模块化(JPMS)后 classpath 配置更复杂
- 不适合频繁高并发动态编译(需缓存 ClassLoader)
九、总结一句话
Java Compiler API 是"把 javac 变成库"的官方标准方案,是构建 Java 代码分析、动态编译、DSL、工具链与平台级能力的基础设施。