JavaParser使用指南

张开发
2026/4/4 16:06:27 15 分钟阅读
JavaParser使用指南
分析 (Analyse)遍历 Java 源码查找你感兴趣的特定模式或结构例如找出所有公共方法或未使用的变量。转换 (Transform)在识别出代码模式后对代码进行修改例如重命名方法、修改逻辑或 添加注解。生成 (Generate)以编程方式动态创建新的 Java 代码例如生成实体类、接口或方法避免重复的手动编写。 快速开始:引入依赖首先你需要在项目中引入 JavaParser 的核心库。在 pom.xml 中添加dependency groupIdcom.github.javaparser/groupId artifactIdjavaparser-core/artifactId version3.28.0/version scopecompile/scope /dependency 核心操作指南1. 分析代码这是最基础的操作目的是将源代码读入并转换为可分析的 AST 结构。解析源代码使用 StaticJavaParser 类可以方便地解析各种形式的 Java 代码。import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import java.io.File; import java.io.FileNotFoundException; public class AnalyzeCode { public static void main(String[] args) throws FileNotFoundException { // 解析一个Java文件 File file new File(path/to/YourClass.java); CompilationUnit cu StaticJavaParser.parse(file); System.out.println(解析完成); } }遍历与查找得到 CompilationUnit(AST根节点)后你可以查找特定的代码元素。按类型查找使用 findAll 方法获取所有指定类型的节点例如所有方法声明。// 获取并打印所有方法的名称 cu.findAll(MethodDeclaration.class).forEach(method - System.out.println(找到方法: method.getNameAsString()) );使用访问者模式对于更复杂的遍历逻辑可以实现 VoidVisitorAdapter 来访问 AST 中的每个节点。import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.ast.body.MethodDeclaration; // 自定义访问者,专门处理MethodDeclaration节点 public class MethodNamePrinter extends VoidVisitorAdapterVoid { Override public void visit(MethodDeclaration md, Void arg) { super.visit(md, arg); // 确保继续遍历子节点 System.out.println(方法名: md.getNameAsString()); } } // 在main方法中使用 // new MethodNamePrinter().visit(cu, null);2. 修改代码在 AST 的基础上你可以直接修改节点属性从而改变代码结构。重命名方法以下示例将所有方法名改为大写。cu.accept(new VoidVisitorAdapterVoid() { Override public void visit(MethodDeclaration n, Void arg) { super.visit(n, arg); String oldName n.getNameAsString(); n.setName(oldName.toUpperCase()); // 直接修改名称 } }, null);修改或添加内容也可以修改更复杂的结构例如替换方法体或添加新语句。// 假设想找到变量名为 oldName 的声明并修改它 cu.findAll(VariableDeclarator.class).forEach(vd - { if (vd.getNameAsString().equals(oldName)) { // 注意:直接修改名称可能不够,还需要处理使用到它的地方 vd.setName(newName); } });3. 生成代码你甚至可以不用解析现有文件直接从零开始构建一个新的 CompilationUnit 来生成代码。import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.stmt.BlockStmt; public class GenerateCode { public static void main(String[] args) { CompilationUnit cu new CompilationUnit(); // 创建一个公共类 MyGeneratedClass ClassOrInterfaceDeclaration myClass cu.addClass(MyGeneratedClass).setPublic(true); // 添加一个私有字段: private String name; myClass.addField(String.class, name, com.github.javaparser.ast.Modifier.Keyword.PRIVATE); // 创建一个公共方法: public void sayHello() { System.out.println(Hello!); } MethodDeclaration method myClass.addMethod(sayHello, com.github.javaparser.ast.Modifier.Keyword.PUBLIC); BlockStmt body new BlockStmt(); body.addStatement(StaticJavaParser.parseStatement(System.out.println(\Hello!\);)); method.setBody(body); // 打印生成的代码 System.out.println(cu.toString()); } }4. 输出代码对 AST 的任何修改最终都可以通过调用 toString() 方法输出为格式化后的源码字符串。你也可以使用 DefaultPrettyPrinterVisitor 自定义输出格式比如缩进空格数。// 输出修改后的代码 String modifiedCode cu.toString(); System.out.println(modifiedCode);5. 实践需求检查代码中的中文字符串是否已经被 I18nUtil 替换并忽略log日志import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.expr.VariableDeclarationExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.FileNotFoundException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class JavaParserTest { private static final ListString strings new ArrayList(); public static void main(String[] args) { // 目录或文件路径 File dir new File(path/to/YourClass.java); // 获取所有导入的包 // cu.getImports().forEach(System.out::println); if (dir.isDirectory()) { traverseDirectory(dir); } else { checkFile(dir); } if (strings.isEmpty()) { System.out.println(改造完成); } else { strings.forEach(System.out::println); } } /** * 检查单个文件 * param file Java 源文件 */ public static void checkFile(File file) { try { CompilationUnit cu StaticJavaParser.parse(file); // 方式1 cu.accept(new MethodVisitor(), file.toPath()); // 方式2 // new MethodVisitor().visit(cu, file.toPath()); } catch (FileNotFoundException e) { System.err.println(文件不存在或无法读取: file.getAbsolutePath()); } } /** * 递归遍历目录,处理所有 Java 文件 * param directory 目录 */ private static void traverseDirectory(File directory) { File[] files directory.listFiles(); if (files null) { return; } for (File file : files) { if (file.isDirectory()) { traverseDirectory(file); } if (file.isFile() file.getName().endsWith(.java)) { checkFile(file); } } } // 自定义访问者,专注于处理方法声明 static class MethodVisitor extends VoidVisitorAdapterPath { Override public void visit(MethodDeclaration method, Path filePath) { super.visit(method, filePath); // 在这里可以进一步处理方法体 method.getBody().ifPresent(body - { //查找所有字符串字面量 body.findAll(StringLiteralExpr.class).forEach(str - { String value str.getValue(); if (containsChinese(value)) { if (!isI18nUtilGetArgument(str)) { String[] split filePath.toString().split(/); String className split[split.length - 1]; // 输出警告 String s String.format([警告] 文件: %s, 方法: %s, 行号: %d, 字符串: \%s\%n, className, method.getNameAsString(), str.getBegin().map(p - p.line).orElse(-1), value); strings.add(s); } } }); }); } } /** * 检查字符串是否包含中文字符 */ private static boolean containsChinese(String s) { for (char c : s.toCharArray()) { if (c \u4e00 c \u9fff) { return true; } } return false; } /** * 判断一个字符串字面量节点是否作为 I18nUtil方法的参数 */ private static boolean isI18nUtilGetArgument(StringLiteralExpr strLiteral) { Node parent strLiteral.getParentNode().orElse(null); // 寻找最近的 MethodCallExpr 祖先节点 while (parent ! null !(parent instanceof MethodCallExpr)) { parent parent.getParentNode().orElse(null); } if (parent instanceof MethodCallExpr) { MethodCallExpr call (MethodCallExpr) parent; // 检查调用者是否为 I18nUtil (可以是标识符或静态引用) OptionalExpression scope call.getScope(); if (scope.isPresent()) { Expression scopeExpr scope.get(); // 忽略log日志 if (StringUtils.equals(scopeExpr.toString(), log)) { return true; } // 判断 I18nUtil if (StringUtils.equals(scopeExpr.toString(), I18nUtil)) { // 确认该字符串字面量是调用的第一个参数(或者某个参数,通常 key 是第一个) // 这里简单判断是否在参数列表中 return call.getArguments().stream() .anyMatch(arg - arg strLiteral); } } } return false; } /** * parseBlock 解析代码块 * 注意:解析的代码块必须包含花括号,否则会报错 */ private static void parseBlock() { // 注意最外层添加了 { 和 },内部语句保持原样 String methodBody {\n int a 10;\n String b \hello\;\n System.out.println(a b);\n // 使用 lambda 表达式\n ListString list Arrays.asList(\a\, \b\, \c\);\n list.forEach(item - System.out.println(item));\n if (a 5) {\n System.out.println(\a is greater than 5\);\n }\n }; try { // 使用 parseBlock 解析代码块 BlockStmt block StaticJavaParser.parseBlock(methodBody); System.out.println(成功解析代码块!共包含 block.getStatements().size() 条语句。\n); // 遍历并分析每条语句 for (Statement stmt : block.getStatements()) { System.out.println(语句类型: stmt.getClass().getSimpleName()); System.out.println(语句内容: stmt); System.out.println(---); } // 示例:查找并修改变量声明 block.findAll(VariableDeclarationExpr.class).forEach(varDecl - { varDecl.getVariables().forEach(variable - { if (variable.getNameAsString().equals(a)) { variable.setName(number); System.out.println(已将变量 a 重命名为 number); } }); }); // 输出修改后的代码 System.out.println(\n修改后的代码块:); System.out.println(block); } catch (Exception e) { System.err.println(解析失败: e.getMessage()); } } }

更多文章