前言
在上一篇文章中,我们了解了AST的基本概念和在编译过程中的位置。但理论知识终究要落地实践,今天我们将从"观察者"变为"操作者",亲手揭开AST的神秘面纱。
你是否想过:
- 如何亲眼看到代码的AST结构?
- 如何遍历AST节点找到特定代码模式?
- 如何修改AST来实现代码自动生成?
本文将带你使用JavaParser这个强大工具,一步步掌握AST的实际操作技能。
环境准备
添加JavaParser依赖
xml
<!-- Maven配置 -->
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.25.4</version>
</dependency>
第一个AST解析程序
java
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
public class FirstASTDemo {
public static void main(String[] args) {
String code =
"public class HelloWorld {\n" +
" public void sayHello() {\n" +
" System.out.println(\"Hello AST!\");\n" +
" }\n" +
"}";
// 解析代码为AST
CompilationUnit cu = StaticJavaParser.parse(code);
// 查看AST结构
System.out.println("=== AST结构 ===");
System.out.println(cu.toString());
}
}
运行这个程序,你会看到完整的AST结构输出。
AST遍历:Visitor模式
理解Visitor设计模式
在AST处理中,Visitor模式让我们能够"访问"每个节点而不修改AST结构。
java
// 类比:博物馆参观者
public interface ASTVisitor {
void visit(ClassDeclaration node); // 参观"类展区"
void visit(MethodDeclaration node); // 参观"方法展区"
void visit(FieldDeclaration node); // 参观"字段展区"
// ... 其他展区
}
实现自定义Visitor
java
public class MethodCounter extends VoidVisitorAdapter<Void> {
private int methodCount = 0;
@Override
public void visit(MethodDeclaration md, Void arg) {
methodCount++;
System.out.println("发现方法: " + md.getName() +
" (参数: " + md.getParameters().size() + ")");
super.visit(md, arg);
}
public int getMethodCount() {
return methodCount;
}
}
使用这个Visitor:
java
public class VisitorDemo {
public static void main(String[] args) {
String code =
"public class Calculator {\n" +
" public int add(int a, int b) { return a + b; }\n" +
" public int multiply(int a, int b) { return a * b; }\n" +
"}";
CompilationUnit cu = StaticJavaParser.parse(code);
MethodCounter counter = new MethodCounter();
counter.visit(cu, null);
System.out.println("总方法数: " + counter.getMethodCount());
}
}
输出:
makefile
发现方法: add (参数: 2)
发现方法: multiply (参数: 2)
总方法数: 2
常用Visitor场景
收集所有字段信息
java
public class FieldCollector extends VoidVisitorAdapter<Void> {
private List<String> fields = new ArrayList<>();
@Override
public void visit(FieldDeclaration fd, Void arg) {
fd.getVariables().forEach(variable -> {
String fieldInfo = String.format("%s %s %s",
fd.getModifiers(), // 访问修饰符
fd.getElementType(), // 字段类型
variable.getName() // 字段名
);
fields.add(fieldInfo);
});
super.visit(fd, arg);
}
public List<String> getFields() {
return fields;
}
}
查找特定方法调用
java
public class MethodCallFinder extends VoidVisitorAdapter<Void> {
private String targetMethod;
private List<String> calls = new ArrayList<>();
public MethodCallFinder(String targetMethod) {
this.targetMethod = targetMethod;
}
@Override
public void visit(MethodCallExpr n, Void arg) {
if (n.getNameAsString().equals(targetMethod)) {
calls.add("在位置: " + n.getRange().map(r -> r.begin.line).orElse(-1));
}
super.visit(n, arg);
}
}
AST结构可视化:理解AST树
递归打印AST树
java
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
public class ASTTreeViewer {
public static void main(String[] args) {
String code =
"public class Calculator {\n" +
" private int value;\n" +
" \n" +
" public int add(int a, int b) {\n" +
" return a + b + value;\n" +
" }\n" +
"}";
CompilationUnit cu = StaticJavaParser.parse(code);
System.out.println("=== AST树状结构 ===");
printASTTree(cu, 0);
}
/**
* 递归打印AST树结构
* @param node 当前节点
* @param depth 当前深度(用于缩进)
*/
public static void printASTTree(Node node, int depth) {
String indent = " ".repeat(depth); // 根据深度生成缩进
String nodeType = node.getClass().getSimpleName();
// 简化的节点内容(避免输出过长)
String content = getNodeSummary(node);
System.out.println(indent + nodeType + ": " + content);
// 递归打印所有子节点
for (Node child : node.getChildNodes()) {
printASTTree(child, depth + 1);
}
}
/**
* 获取节点的简化信息
*/
private static String getNodeSummary(Node node) {
String fullString = node.toString().split("\n")[0]; // 取第一行
if (fullString.length() > 50) {
return fullString.substring(0, 47) + "...";
}
return fullString;
}
}
运行结果示例
yaml
=== AST树状结构 ===
CompilationUnit: public class Calculator {
ClassOrInterfaceDeclaration: public class Calculator {
Modifier: public
SimpleName: Calculator
FieldDeclaration: private int value;
Modifier: private
VariableDeclarator: value
PrimitiveType: int
SimpleName: value
MethodDeclaration: public int add(int a, int b) {
Modifier: public
SimpleName: add
Parameter: int a
PrimitiveType: int
SimpleName: a
Parameter: int b
PrimitiveType: int
SimpleName: b
PrimitiveType: int
BlockStmt: {
ReturnStmt: return a + b + value;
BinaryExpr: a + b + value
BinaryExpr: a + b
NameExpr: a
SimpleName: a
NameExpr: b
SimpleName: b
NameExpr: value
SimpleName: value
AST树结构解析
树的层次关系
scss
CompilationUnit (根节点 - 整个文件)
└── ClassOrInterfaceDeclaration (类声明)
├── Modifier (修饰符: public)
├── SimpleName (类名: Calculator)
├── FieldDeclaration (字段声明)
│ ├── Modifier (修饰符: private)
│ └── VariableDeclarator (变量声明器)
│ └── SimpleName (字段名: value)
└── MethodDeclaration (方法声明)
├── Modifier (修饰符: public)
├── SimpleName (方法名: add)
├── Parameter (参数)
│ ├── PrimitiveType (类型: int)
│ └── SimpleName (参数名: a)
├── BlockStmt (方法体)
│ └── ReturnStmt (返回语句)
│ └── BinaryExpr (二元表达式: a + b + value)
│ ├── BinaryExpr (子表达式: a + b)
│ │ ├── NameExpr (变量a)
│ │ └── NameExpr (变量b)
│ └── NameExpr (变量value)
关键节点类型说明
| 节点类型 | 说明 | 示例 |
|---|---|---|
CompilationUnit |
整个编译单元 | 整个.java文件 |
ClassOrInterfaceDeclaration |
类或接口声明 | class Calculator |
MethodDeclaration |
方法声明 | public int add(...) |
FieldDeclaration |
字段声明 | private int value |
VariableDeclarator |
变量声明器 | value (字段名) |
BlockStmt |
代码块 | { ... } |
ReturnStmt |
返回语句 | return a + b |
BinaryExpr |
二元表达式 | a + b, x > y |
NameExpr |
名称表达式 | a, b, value |
AST修改:实现代码自动生成
为类添加字段
java
public class ClassModifier {
public static void addField(CompilationUnit cu,
String fieldName,
String fieldType) {
// 为 user 类添加字段
cu.getClassByName("User").ifPresent(clazz -> {
// 构建字段代码
String fieldCode = "private " + fieldType + " " + fieldName + ";";
// 解析为AST节点并添加
var field = StaticJavaParser.parseBodyDeclaration(fieldCode);
clazz.addMember(field);
System.out.println("成功添加字段: " + fieldName);
});
}
}
自动生成Getter方法
java
public class GetterGenerator extends VoidVisitorAdapter<Void> {
@Override
public void visit(ClassOrInterfaceDeclaration cid, Void arg) {
// 为每个字段生成getter
cid.getFields().forEach(field -> {
field.getVariables().forEach(variable -> {
String fieldName = variable.getNameAsString();
String fieldType = field.getElementType().asString();
generateGetter(cid, fieldName, fieldType);
});
});
super.visit(cid, arg);
}
private void generateGetter(ClassOrInterfaceDeclaration clazz,
String fieldName, String fieldType) {
String getterName = "get" + capitalize(fieldName);
String getterCode = String.format(
"public %s %s() { return this.%s; }",
fieldType, getterName, fieldName
);
try {
var getter = StaticJavaParser.parseBodyDeclaration(getterCode);
clazz.addMember(getter);
System.out.println("生成Getter: " + getterName);
} catch (Exception e) {
System.err.println("生成Getter失败: " + e.getMessage());
}
}
private String capitalize(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}