tools.jar (JDK 9+ 為 jdk.compiler 模塊) 提供標準化 Compiler Tree API,無需第三方依賴。$JAVA_HOME/lib/tools.jar 加入類路徑。module-info.java 聲明 requires jdk.compiler; 或啟動參數 --add-modules jdk.compiler。<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>${java.version}</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
| 類/接口 | 作用 |
|---|---|
javax.tools.JavaCompiler | 編譯器入口,ToolProvider.getSystemJavaCompiler() 獲取 |
javax.tools.JavaFileObject | 源文件抽象(內存/磁盤/ZIP 均可) |
com.sun.source.tree.CompilationUnitTree | 單個源文件的語法樹根節點 |
com.sun.source.util.TreeScanner | 訪問者模式遍歷語法樹的基類 |
com.sun.source.util.Trees | 工具類:節點 ↔ Element/Symbol 映射、位置信息 |
public class VariableExtractor {
public static void main(String[] args) throws IOException {
// 1. 構建編譯任務
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
Iterable extends JavaFileObject> files = fm.getJavaFileObjects("src/main/java/com/example/User.java");
JavaCompiler.CompilationTask task = compiler.getTask(null, fm, null, null, null, files);
// 2. 解析獲取語法樹
Iterable extends CompilationUnitTree> units = task.parse();
// 3. 遍歷
for (CompilationUnitTree unit : units) {
unit.accept(new VariableVisitor(), null);
}
}
static class VariableVisitor extends TreeScanner {
private String currentPackage = "";
@Override
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
currentPackage = node.getPackageName().toString();
return super.visitCompilationUnit(node, p);
}
@Override
public Void visitVariable(VariableTree node, Void p) {
// 過濾局部變量:僅保留字段
if (node.getKind() == ElementKind.FIELD) {
List extends AnnotationTree> annos = node.getModifiers().getAnnotations();
String annoStr = annos.isEmpty() ? "無" : annos.get(0).getAnnotationType().toString();
System.out.printf("包:%s | 字段:%s | 類型:%s | 註解:%s%n",
currentPackage, node.getName(), node.getType(), annoStr);
}
return super.visitVariable(node, p);
}
}
}
User.javapackage com.example;
import com.sun.istack.internal.Nullable;
public class User {
@Nullable private String foo = "123";
private Foo bar;
public void setFoo(String foo) { this.foo = foo; }
static class Foo { private String inner; }
}
包:com.example | 字段:foo | 類型:String | 註解:@Nullable
包:com.example | 字段:bar | 類型:Foo | 註解:無
static class ClassStructureVisitor extends TreeScanner {
@Override
public Void visitClass(ClassTree node, Void p) {
System.out.println("=== 類: " + node.getSimpleName() + " ===");
for (Tree member : node.getMembers()) {
switch (member.getKind()) {
case VARIABLE -> {
VariableTree v = (VariableTree) member;
System.out.println(" 字段: " + v.getName() + " : " + v.getType());
}
case METHOD -> {
MethodTree m = (MethodTree) member;
System.out.println(" 方法: " + m.getName() + m.getParameters() + " -> " + m.getReturnType());
// 遞歸進入方法體
m.getBody().accept(new StatementPrinter(), null);
}
default -> {}
}
}
return super.visitClass(node, p);
}
}
static class StatementPrinter extends TreeScanner {
@Override public Void visitIf(IfTree node, Void p) {
System.out.println(" IF: " + node.getCondition());
return super.visitIf(node, p);
}
@Override public Void visitVariable(VariableTree node, Void p) {
System.out.println(" 區域變量: " + node.getName());
return super.visitVariable(node, p);
}
@Override public Void visitExpressionStatement(ExpressionStatementTree node, Void p) {
System.out.println(" 語句: " + node.getExpression());
return super.visitExpressionStatement(node, p);
}
}
Tree API 僅提供語法層面的類型名(如 List 只能拿到 List),要獲取 **語義級類型**(含泛型參數、全限定名),需結合 Trees 工具類與 Element 模型:
Trees trees = Trees.instance(task); // task 為 CompilationTask
for (CompilationUnitTree unit : units) {
for (Tree typeDecl : unit.getTypeDecls()) {
if (typeDecl instanceof ClassTree clazz) {
Element classElem = trees.getElement(trees.getPath(unit, clazz));
TypeMirror classType = classElem.asType(); // 完整類型
System.out.println("類完整類型: " + classType);
for (VariableTree field : clazz.getMembers().stream()
.filter(t -> t.getKind() == Tree.Kind.VARIABLE)
.map(t -> (VariableTree) t).toList()) {
Element fieldElem = trees.getElement(trees.getPath(unit, field));
System.out.println(" 字段完整類型: " + fieldElem.asType()); // 含泛型參數
}
}
}
}
com.sun.source.* 屬於 JDK 內部 API,版本間可能變動,生產工具建議封裝適配層。StandardJavaFileManager 默認平台編碼,中文項目請顯式指定 StandardCharsets.UTF_8。cannot find symbol 但不拋異常,需檢查 task.analyze() 或 diagnosticListener。CompilationUnitTree。JDK Compiler Tree API 提供了 **純 Java、零依賴** 的源碼靜態分析能力。掌握 TreeScanner 訪問者模式與 Trees 語義映射,即可構建變量提取、方法調用鏈分析、註解處理器等各類元編程工具。Lombok、MapStruct、MyBatis-Plus 等知名庫均基於此原理實現編譯期增強。