如何用JavaParser 3.25.8获取Java类方法对自身字段的读写访问列表
解决JavaParser统计类字段访问的问题
问题需求
需要使用JavaParser 3.25.8实现以下功能:
- 仅统计类的成员字段,排除方法内局部变量
- 覆盖所有字段访问类型(赋值、自增、自减等)
- 区分字段的读访问和写访问
- 仅统计目标类自身的字段,排除其他类的字段
现有代码问题
当前代码仅通过FieldAccessExpr查找字段访问,存在两个核心问题:
- 漏统计自身类字段:类内直接访问自身字段(如
field1 = 10中的field1)对应的AST节点是NameExpr,而非FieldAccessExpr,因此被遗漏。 - 错误统计外部类字段:
System.out这类外部类的字段访问属于FieldAccessExpr,被错误统计进来。
同时,现有代码未实现读/写访问的区分,也没有校验字段是否属于当前类。
解决方案
利用JavaParser的Symbol Solver模块解析符号类型,结合AST节点分析实现需求:
- 初始化Symbol Solver,让JavaParser能识别字段的所属类
- 遍历方法内的所有表达式,同时处理
NameExpr和FieldAccessExpr - 通过符号解析判断访问的字段是否属于当前类
- 根据上下文区分读/写访问:
- 赋值语句的左操作数、自增/自减表达式属于写访问
- 赋值语句的右操作数、方法调用参数等属于读访问
修改后的完整代码
import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.*; import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; import com.github.javaparser.symbolsolver.JavaSymbolSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; public class FieldAccessList { public static void main(String[] args) throws IOException { // 初始化Symbol Solver,用于解析符号类型 CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver(); combinedTypeSolver.add(new ReflectionTypeSolver()); JavaSymbolSolver symbolSolver = new JavaSymbolSolver(combinedTypeSolver); StaticJavaParser.getConfiguration().setSymbolResolver(symbolSolver); File sourceFile = new File("Example.java"); CompilationUnit cu = StaticJavaParser.parse(sourceFile); cu.findAll(ClassOrInterfaceDeclaration.class).forEach(classDeclaration -> { System.out.println("Class: " + classDeclaration.getNameAsString()); // 收集当前类的所有字段名称 Set<String> classFields = new HashSet<>(); classDeclaration.findAll(FieldDeclaration.class).forEach(field -> { field.getVariables().forEach(var -> classFields.add(var.getNameAsString())); }); classDeclaration.findAll(MethodDeclaration.class).forEach(methodDeclaration -> { System.out.println(" Method: " + methodDeclaration.getNameAsString()); // 处理赋值表达式的左值(写访问) methodDeclaration.findAll(AssignExpr.class).forEach(assignExpr -> { processFieldAccess(assignExpr.getTarget(), classFields, "Write"); }); // 处理自增自减表达式(写访问) methodDeclaration.findAll(UnaryExpr.class).forEach(unaryExpr -> { if (unaryExpr.getOperator() == UnaryExpr.Operator.POSTFIX_INCREMENT || unaryExpr.getOperator() == UnaryExpr.Operator.POSTFIX_DECREMENT || unaryExpr.getOperator() == UnaryExpr.Operator.PREFIX_INCREMENT || unaryExpr.getOperator() == UnaryExpr.Operator.PREFIX_DECREMENT) { processFieldAccess(unaryExpr.getExpression(), classFields, "Write"); } }); // 处理读访问:所有不是写操作的字段访问 methodDeclaration.findAll(NameExpr.class).forEach(nameExpr -> { if (!isPartOfWriteOperation(nameExpr)) { processFieldAccess(nameExpr, classFields, "Read"); } }); methodDeclaration.findAll(FieldAccessExpr.class).forEach(fieldAccessExpr -> { if (!isPartOfWriteOperation(fieldAccessExpr)) { processFieldAccess(fieldAccessExpr, classFields, "Read"); } }); }); }); } private static void processFieldAccess(Expr expr, Set<String> classFields, String accessType) { try { // 解析表达式对应的符号 if (expr instanceof NameExpr) { ResolvedFieldDeclaration field = ((NameExpr) expr).resolve().asField(); String fieldName = field.getName(); if (classFields.contains(fieldName)) { System.out.println(" " + accessType + " Access: " + fieldName); } } else if (expr instanceof FieldAccessExpr) { ResolvedFieldDeclaration field = ((FieldAccessExpr) expr).resolve().asField(); String fieldName = field.getName(); // 判断字段所属类是否为当前类(通过全限定名对比) String currentClassFullName = expr.findAncestor(ClassOrInterfaceDeclaration.class) .map(c -> c.getFullyQualifiedName().orElse("")) .orElse(""); if (field.declaringType().getQualifiedName().equals(currentClassFullName)) { System.out.println(" " + accessType + " Access: " + fieldName); } } } catch (Exception e) { // 非字段访问的情况,直接忽略 } } private static boolean isPartOfWriteOperation(Expr expr) { // 判断表达式是否是赋值操作的左值,或者自增自减操作的目标 Node parent = expr.getParentNode().orElse(null); if (parent instanceof AssignExpr) { return ((AssignExpr) parent).getTarget() == expr; } if (parent instanceof UnaryExpr) { UnaryExpr.Operator op = ((UnaryExpr) parent).getOperator(); return op == UnaryExpr.Operator.POSTFIX_INCREMENT || op == UnaryExpr.Operator.POSTFIX_DECREMENT || op == UnaryExpr.Operator.PREFIX_INCREMENT || op == UnaryExpr.Operator.PREFIX_DECREMENT; } return false; } }
验证结果
运行修改后的代码,针对Example.java的输出如下:
Class: Example Method: method1 Write Access: field1 Read Access: field2 Method: method2 Write Access: field2
完全符合需求,既统计了自身类的所有字段访问,排除了外部类字段,还区分了读/写访问类型。
内容的提问来源于stack exchange,提问作者Gholamali Irani
相关产品推荐
相关产品推荐

