文章目录

一、简介二、基本的类和概念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

com.github.javaparser

javaparser-core

3.23.1

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 arg) {

//打印方法名称

String methodName = n.getName().getIdentifier();

arg.add(methodName);

System.out.println(methodName);

//调用父类方法是为了递归调用下去,可能子节点还有当前类型的Node

super.visit(n, arg);

}

};

//方法集合

List methodNames = new ArrayList<>();

//接收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 result = new ArrayList<>();

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 modifyFile(Path file) throws IOException {

CompilationUnit cu = StaticJavaParser.parse(file);

//统计修改的接口

List list = new ArrayList<>();

//遍历所有类,文件中不止定义一个类

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 arg) {

//方法有忽略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 optional = cls.getAnnotations()

.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 optional = method.getAnnotations()

.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 expr = method.getAnnotationByName("RequestMapping");

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 result) throws IOException {

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;

}

}

}

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。