当前位置: 首页 > news >正文

JavaParser使用指南 - 杯酒

JavaParser 是一个功能强大的 Java 库,用于解析、分析和修改 Java 源代码。它通过将代码转换为抽象语法树(AST)来进行操作,广泛应用于自动化重构、代码生成和静态分析等场景。

下面将为你详细介绍它的核心用法。

💡 JavaParser 能做什么?

JavaParser 主要有三大应用场景:

  • 分析 (Analyse):遍历 Java 源码,查找你感兴趣的特定模式或结构,例如找出所有公共方法或未使用的变量。
  • 转换 (Transform):在识别出代码模式后,对代码进行修改,例如重命名方法、修改逻辑或 添加注解。
  • 生成 (Generate):以编程方式动态创建新的 Java 代码,例如生成实体类、接口或方法,避免重复的手动编写。

🚀 快速开始:引入依赖

首先,你需要在项目中引入 JavaParser 的核心库。在 pom.xml 中添加:

<dependency><groupId>com.github.javaparser</groupId><artifactId>javaparser-core</artifactId><version>3.28.0</version><scope>compile</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 VoidVisitorAdapter<Void> {@Overridepublic 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 VoidVisitorAdapter<Void>() {@Overridepublic 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 List<String> 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);// 方式1cu.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 VoidVisitorAdapter<Path> {@Overridepublic 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" (可以是标识符或静态引用)Optional<Expression> scope = call.getScope();if (scope.isPresent()) {Expression scopeExpr = scope.get();// 忽略log日志if (StringUtils.equals(scopeExpr.toString(), "log")) {return true;}// 判断 I18nUtilif (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" +" List<String> 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());}}
}
http://www.jsqmd.com/news/421588/

相关文章:

  • 2026年哈尔滨口碑好的抖音同城运营,抖音本地推,抖音本地商家运营公司优质品牌推荐榜 - 品牌鉴赏师
  • 试填法+动态计数
  • 2026年低代码平台最新盘点:15款标杆厂商深度解析与选型指南
  • 2026年减温减压装置厂家推荐排行榜:蒸汽/一体式/分体式/气动/电动/锅炉/汽轮机减温减压装置设备精选 - 品牌企业推荐师(官方)
  • 2026年 广告招牌厂家推荐排行榜:3D打印发光字、铝合金型材、罗马克实心字、轨道展厅发光字工厂精选 - 品牌企业推荐师(官方)
  • 精工智造,守护精密:上海临港专业电子吸塑托盘厂家解析
  • Flutter 项目结构该如何设计,才能支撑长期迭代
  • 企业短信平台选云厂商还是垂直短信平台? - Qqinqin
  • 2026年重庆公办职高精选 实力出众口碑优良 适配各类学子发展 升学就业有保障 - 深度智识库
  • 2026年 编织袋厂家推荐排行榜:饲料/化肥/大米/种子/粮食/玉米/腻子粉/砂浆/化工/矿山编织袋,专业定制与耐用品质之选 - 品牌企业推荐师(官方)
  • 记一次vs中无法找到win sdk的问题
  • 苏剑林苏神科学空间:一个数学极客的十六年技术坚守
  • 国产恒温恒湿试验箱怎么选?泰美科昆山研发+襄阳生产全维度解析 - 博客万
  • 商场、办公楼与酒店香氛香薰推荐,解锁商业空间嗅觉密码 - 包罗万闻
  • 预算有限怎么选:十大留学机构分级服务指南 - 博客湾
  • 2026年如何从砖机公司排名中选择知名的路沿石砖机厂家? - 睿易优选
  • 代理模式——基本介绍
  • 2025年货架选购攻略:哪些功能是必备?重型货架/仓储货架/隔板货架/层板货架/贯通式货架,货架源头厂家排行榜 - 品牌推荐师
  • 解决连接无法继续,因为未启用身份验证,并且远程计算机需要启用身份验证以进行连接
  • 十大香港留学机构!保障港申材料翻译的专业性 - 博客湾
  • 二元运算符
  • 为什么你在百度排第一,客户却选了竞争对手?
  • Continual-MEGA—Arxiv2026—阅读笔记
  • 2026年重庆卫校哪家靠谱?实力强劲口碑优良 适配不同学子需求 - 深度智识库
  • Python为何要使用代理IP?
  • 2026年 环境监测仪器厂家推荐排行榜:光合作用监测仪、气体监测仪、水质监测仪等专业设备实力品牌深度解析 - 品牌企业推荐师(官方)
  • 2026年有实力的活性氧化铝除氟剂,活性氧化铝干燥剂厂家选购参考指南 - 品牌鉴赏师
  • 2026年口碑好的分子筛干燥剂厂家选购参考名录 - 品牌鉴赏师
  • LeetCode 394 字符串解码
  • 2026年 南通竞价托管外包服务商推荐榜:专业搜索引擎竞价推广与外包代操作一站式解决方案 - 品牌企业推荐师(官方)