文章目录
一、简介二、基本的类和概念1.CompilationUnit2.Node3.Visitor3.1 Visitable和两类Visitor接口3.2 VoidVisitorAdapter3.3 ModifierVisitor
三、实践1.依赖gradlemaven
2.收集java文件中的全部方法名3.修改特定的controller请求路径
一、简介
Javaparser是一个通过生成语法树来读取或操作java代码的库。它的github地址https://github.com/javaparser/javaparser,官方文档在https://leanpub.com/javaparservisited。
二、基本的类和概念
1.CompilationUnit
Javaparser解析java代码后会生成AST(abstract syntax tree,抽象语法树),CompilationUnit(编译单元)是每个java文件被解析后直接生成的对象,是读取和操作java文件的入口。CompilationUnit包含了整棵AST的Node,可以理解为AST的根节点。 上图根据代码画出了CompilationUnit的结构,每个节点都继承自Node。
2.Node
AST的Node,对于java中的类、接口、注解、方法、入参、赋值语句、注释、if条件、注释等都是一种Node,如果Node表示的代码块能继续细化分割,就在其子节点列表NodeList中,Node是读取和操作AST的基本单元。
Node的部分子类: 这些Node类和其表示的java代码(每个类的注释中有,官方文档附录B中有全部的Node类及其示例)
3.Visitor
Javaparser使用访问者模式来访问或修改Node,当需要修改Node时,Node本身不需要额外增加方法,而是通过创建一个Visitor,在Visitor中定义好需要修改什么,用Node调用方法接收我们创建的Visitor完成修改。
3.1 Visitable和两类Visitor接口
为了实现这种模式,作者设计了两类接口,一个是Visitable,一个是Visitor(根据有无返回值,分为GenericVisitor和VoidVisitor)。
(1)所有可访问的Node都实现了Visitable接口,这个接口有两个accept方法,用于接收Visitor以及外部参数arg,外部参数可用于收集遍历到的东西。 (2)所有Node实现Visitable接口的方法都是传递自身实例和外部arg。
@Generated是作者生成accept代码后加上的,作者比较“懒”,编写了很多generator生成代码,下图这段代码是由AcceptGenerator生成的。
(3)针对不同类型Node,实现GenericVisitor接口中对应的方法就能访问这种类型的Node。 也就是说我们用Node实例调用它的accept方法,传入一个编写好的Visitor,Visitor中的实现方法就能访问这个Node。
3.2 VoidVisitorAdapter
当我们只需要访问java代码而不需要做修改时,直接继承VoidVisitorAdapter。这个抽象类对VoidVisitor做了默认实现,通过递归执行accept方法来达到遍历整个AST的目的。 上图中对CompilationUnit的import(引包)、module(java高版本模块)、package(所属包)、type(定义的类型class、interface、enum、annotation)、comment(注释)分别遍历执行accept,而其中的每一种Node又会遍历子节点执行accept。例如下图访问class和interface的ClassOrInterfaceDeclaration的方法,分别遍历它涵盖的节点。
3.3 ModifierVisitor
与VoidVisitorAdapter不同的是,ModifierVisitor继承带返回值的GenericVisitor,其返回值用于返回修改后的节点。如下图所示,遍历各项子节点以后,将返回值作为修改后的对象重新赋值。
三、实践
1.依赖
gradle
// https://mvnrepository.com/artifact/com.github.javaparser/javaparser-core
implementation 'com.github.javaparser:javaparser-core:3.23.1'
maven
2.收集java文件中的全部方法名
如果我们想打印并收集一个java文件中所有的方法名,继承VoidVisitorAdapter。
public static void main(String[] args) throws IOException {
//FIXME
Path path = Paths.get("your_java_file.java");
//解析java文件生成AST
CompilationUnit cu = StaticJavaParser.parse(path);
//打印并收集所有方法的Visitor
VoidVisitor> printMethodVisitor = new VoidVisitorAdapter
>() {
@Override
public void visit(MethodDeclaration n, List
//打印方法名称
String methodName = n.getName().getIdentifier();
arg.add(methodName);
System.out.println(methodName);
//调用父类方法是为了递归调用下去,可能子节点还有当前类型的Node
super.visit(n, arg);
}
};
//方法集合
List
//接收Visitor执行
cu.accept(printMethodVisitor, methodNames);
for (String name : methodNames) {
System.out.println(name);
}
}
3.修改特定的controller请求路径
对使用了忽略token注解@Ignoretoken的controller方法,请求路径上添加一个/ignore。
简单描述下需求: 首先需要找到全部Controller,然后获取controller的请求path,再遍历所有带有RequestMapping、GetMapping或PostMapping的方法,对有忽略token注解的方法,修改其请求path的值,记录新旧路径,最后输出到excel中。
import com.github.javaparser.StaticJavaParser;
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.expr.*;
import com.github.javaparser.ast.visitor.ModifierVisitor;
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.printer.DefaultPrettyPrinter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 修改controller请求路径
*
* @author HetFrame
*/
public class ModifyControllerPath {
//TODO 修改为你的项目地址
private static final Path ROOT_PATH = Paths.get("your_project\\src\\main\\java");
//TODO excel导出路径
private static final String outPath = "D:\\ModifiedController.xlsx";
public static void main(String[] args) throws IOException {
List
Files.walk(ROOT_PATH)
//只获取controller文件
.filter(path -> path.toString().endsWith("Controller.java"))
.forEach(path -> {
try {
result.addAll(modifyFile(path));
} catch (IOException e) {
e.printStackTrace();
}
});
// 在遍历文件后,导出到Excel
exportToExcel(result);
}
private static List
CompilationUnit cu = StaticJavaParser.parse(file);
//统计修改的接口
List
//遍历所有类,文件中不止定义一个类
cu.findAll(ClassOrInterfaceDeclaration.class).forEach(cls -> {
//获取controller上RequestMapping的路径
String controllerPath = getControllerPath(cls);
if (controllerPath == null) {
return;
}
controllerPath = controllerPath.replace("\"", "");
String finalControllerPath = controllerPath;
//修改每个controller中的方法
cls.accept(new ModifierVisitor>() {
@Override
public Visitable visit(MethodDeclaration n, List
//方法有忽略token注解
if (hasIgnoreTokenAnnotation(n)) {
String methodPath = getMethodPath(n).replace("\"", "");
//修改请求路径
String modifiedPath = modifyPath(methodPath);
//注解赋值有两种情况所以实现了两个方法
n.accept(new ModifierVisitor
@Override
public Visitable visit(SingleMemberAnnotationExpr n, Void arg) {
String annotationName = n.getName().getIdentifier();
if (annotationName.equals("RequestMapping") || annotationName.equals("GetMapping") || annotationName.equals("PostMapping")) {
Expression expression = n.getMemberValue().clone();
expression.asStringLiteralExpr().setString(modifiedPath);
n.setMemberValue(expression);
}
return super.visit(n, arg);
}
@Override
public Visitable visit(NormalAnnotationExpr n, Void arg) {
String annotationName = n.getName().getIdentifier();
if (annotationName.equals("RequestMapping") || annotationName.equals("GetMapping") || annotationName.equals("PostMapping")) {
MemberValuePair pair = n.getPairs().stream().filter(e -> "value".equals(e.getName().toString())).findFirst().orElse(new MemberValuePair());
if (pair.getValue() != null) {
Expression expression = pair.getValue().clone();
expression.asStringLiteralExpr().setString(modifiedPath);
pair.setValue(expression);
}
}
return super.visit(n, arg);
}
}, null);
//将改动信息存入list
String fullOriginalPath = finalControllerPath + methodPath;
String fullModifiedPath = finalControllerPath + modifiedPath;
String controllerName = cls.getNameAsString();
String requestType = getRequestType(n);
arg.add(new ControllerPathInfo(controllerName, requestType, fullOriginalPath, fullModifiedPath));
}
return super.visit(n, arg);
}
}, list);
});
//获取修改后的java代码并写入文件 DefaultPrettyPrinter用于格式化代码
String outputStr = new DefaultPrettyPrinter().print(cu);
FileWriter writer = new FileWriter(file.toFile());
writer.write(outputStr);
writer.close();
return list;
}
private static String getControllerPath(ClassOrInterfaceDeclaration cls) {
Optional
.stream()
.filter(a -> a.getNameAsString().equals("RequestMapping"))
.findFirst();
if (optional.isPresent()) {
AnnotationExpr expr = optional.get();
if (expr.isSingleMemberAnnotationExpr()) {
return expr.asSingleMemberAnnotationExpr().getMemberValue().toString();
}
if (expr.isNormalAnnotationExpr()) {
return expr.asNormalAnnotationExpr().getPairs().stream().filter(pair -> "value".equals(pair.getName().toString())).findFirst().get().getValue().toString();
}
}
return null;
}
private static boolean hasIgnoreTokenAnnotation(MethodDeclaration method) {
return method.getAnnotationByName("IgnoreToken").isPresent();
}
private static String getMethodPath(MethodDeclaration method) {
Optional
.stream()
.filter(a -> a.getNameAsString().equals("RequestMapping") || a.getNameAsString().equals("GetMapping") || a.getNameAsString().equals("PostMapping"))
.findFirst();
if (optional.isPresent()) {
AnnotationExpr expr = optional.get();
if (expr.isSingleMemberAnnotationExpr()) {
return expr.asSingleMemberAnnotationExpr().getMemberValue().toString();
}
if (expr.isNormalAnnotationExpr()) {
return expr.asNormalAnnotationExpr().getPairs().stream().filter(pair -> "value".equals(pair.getName().toString())).findFirst().get().getValue().toString();
}
}
throw new RuntimeException("未找到method请求路径");
}
private static String modifyPath(String originalPath) {
//请求路径风格为/v1/xxx 或 /{version}/xxx
return originalPath.replaceAll("/(v1|\\{version\\})/", "/$1/ignore/");
}
private static String getRequestType(MethodDeclaration method) {
if (method.getAnnotationByName("GetMapping").isPresent()) {
return "GET";
}
if (method.getAnnotationByName("PostMapping").isPresent()) {
return "POST";
}
Optional
if (expr.isPresent()) {
Expression type = expr.get().asNormalAnnotationExpr().getPairs().stream().filter(pair -> "method".equals(pair.getName().toString())).findFirst().get().getValue();
if (type.toString().contains("GET")) {
return "GET";
}
if (type.toString().contains("POST")) {
return "POST";
}
}
// add other request types as needed
return "UNKNOWN";
}
private static void exportToExcel(List
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Modified Interfaces");
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("接口controller");
headerRow.createCell(1).setCellValue("请求类型");
headerRow.createCell(2).setCellValue("旧接口地址");
headerRow.createCell(3).setCellValue("新接口地址");
for (int i = 0; i < result.size(); i++) {
ControllerPathInfo interfaceInfo = result.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(interfaceInfo.getController());
row.createCell(1).setCellValue(interfaceInfo.getRequestType());
row.createCell(2).setCellValue(interfaceInfo.getOldPath());
row.createCell(3).setCellValue(interfaceInfo.getNewPath());
}
try (FileOutputStream outputStream = new FileOutputStream(outPath)) {
workbook.write(outputStream);
}
workbook.close();
}
public static class ControllerPathInfo {
private String controller;
private String requestType;
private String oldPath;
private String newPath;
public ControllerPathInfo(String controller, String requestType, String oldPath, String newPath) {
this.controller = controller;
this.requestType = requestType;
this.oldPath = oldPath;
this.newPath = newPath;
}
public String getController() {
return controller;
}
public String getRequestType() {
return requestType;
}
public String getOldPath() {
return oldPath;
}
public String getNewPath() {
return newPath;
}
}
}
相关链接
发表评论