Java SE 6 引入三大標準 API——Java Compiler API (JSR 199)、可插拔注解處理 API (JSR 269)、Compiler Tree API,使開發者能以標準化方式編程訪問編譯器、解析源碼、構建抽象語法樹(AST),實現代碼分析、自動修復、規則檢查等 IDE 級功能。本文通過構建一個驗證 equals/hashCode 契約的演示工具,全面講解三大 API 協作原理與代碼實現。
| API | 包 | 核心作用 |
|---|---|---|
| Java Compiler API | javax.tools | 程序化調用編譯器,管理編譯任務、文件管理、診斷收集 |
| 可插拔注解處理 API | javax.annotation.processing, javax.lang.model | 標準化注解處理器編寫與插入,替代舊 APT 工具,支持多輪處理 |
| Compiler Tree API | com.sun.source.tree, com.sun.source.util | 提供源碼只讀 AST 視圖,支持 TreeVisitor/TreeScanner 遍歷提取類、方法、字段等結構信息 |
規則:覆寫 equals() 的類必須覆寫 hashCode()。待分析類:
public class TestClass implements Serializable {
int num;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
TestClass test = (TestClass) obj;
return num == test.num;
}
// 缺少 hashCode() 實現
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable extends JavaFileObject> units = fileManager.getJavaFileObjectsFromFiles("TestClass.java");
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
task.call(); // 執行編譯,生成 TestClass.class
繼承 AbstractProcessor,覆寫 process(),聲明支持所有注解(@SupportedAnnotationTypes("*"))以觸發通用源碼分析:
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("*")
public class CodeAnalyzerProcessor extends AbstractProcessor {
private CodeAnalyzerTreeVisitor visitor = new CodeAnalyzerTreeVisitor();
@Override
public void init(ProcessingEnvironment pe) {
super.init(pe);
visitor.trees = Trees.instance(pe); // 獲取 Trees 實例,橋接 JSR 269 與 Tree API
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element e : roundEnv.getRootElements()) {
TreePath path = visitor.trees.getPath(e);
visitor.scan(path, visitor.trees); // 掃描 AST
}
return true; // 聲明已處理,阻止後續處理器處理同類注解
}
}
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
List processors = new ArrayList<>();
processors.add(new CodeAnalyzerProcessor());
task.setProcessors(processors); // 必須在 call() 前設置
task.call();
public class CodeAnalyzerTreeVisitor extends TreePathScanner
利用 SourcePositions 和 LineMap 獲取節點在編譯單元中的字符偏移,再通過正則匹配令牌精確定位:
SourcePositions sp = trees.getSourcePositions();
long start = sp.getStartPosition(path.getCompilationUnit(), node);
int line = compileTree.getLineMap().getLineNumber((int) start);
規則配置於 XML,RuleEngine 逐條執行。OverrideEqualsHashCode 規則邏輯:
public class OverrideEqualsHashCode extends JavaClassRule {
protected Collection apply(ClassFile clazz) {
boolean hasEquals = false, hasHashCode = false;
Location errorLoc = null;
for (Method m : clazz.getMethods()) {
if ("equals".equals(m.getName()) && m.getParameters().size() == 1
&& "java.lang.Object".equals(m.getParameters().get(0))) {
hasEquals = true;
errorLoc = m.getLocationInfo();
} else if ("hashCode".equals(m.getName()) && m.getParameters().isEmpty()) {
hasHashCode = true;
}
}
if (hasEquals && !hasHashCode) {
// 返回包含錯誤消息與位置的 ErrorDescription
}
return null;
}
}
命令行運行:
java -classpath \lib\tools.jar;. demo.codeanalyzer.main.Main
擴展方向:
javax.lang.model 進行更深層語義分析(類型推導、數據流)。Java 6 三大編譯器 API 打破了 IDE 專有解析器的壟斷,提供標準化、可重用的源碼分析基礎設施。掌握其協作模式——Compiler API 驅動編譯流程,Annotation Processing API 切入處理鉤子,Tree API 提供結構化源碼視圖**——是構建高質量靜態分析工具、代碼生成器、重構引擎的關鍵。