问题描述
公司业务迭代通过开关进行风险规避,在开关打开时走新业务逻辑,关闭时走老业务逻辑。这导致有大量的开关代码遗留,每次人工清理开关容易遗漏出错。为了减少出错概率,将清理开关动作进行自动化
解决方案
使用JDT工具解析java源码代码进行针对开关的语义自动替换清理
JDT使用
引入依赖
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.16.0</version>
</dependency>
生成抽象语法树,重写AST
public static void switchClean(String javaFilePath) throws Exception {
// 1. 读取源文件代码
String src;
try {
src = FileUtils.readFileToString(new File(javaFilePath), "utf8");
} catch (Exception var5) {
log.error("读取源文件失败", var5);
return;
}
if (StringUtils.isBlank(src)) {
log.error("源文件为空");
return;
}
// 2. 根据源代码创建dom
Document document= new Document(src);
// 3. 根据规范创建ASTParser(抽象语法树解析器)
ASTParser astParser = ASTParser.newParser(AST.JLS11);
// 4. 创建编译参数配置
Map<String, String> compilerOptions = JavaCore.getOptions();
// 5. 指定编译参数,1.8 jdk
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, compilerOptions);
astParser.setCompilerOptions(compilerOptions);
astParser.setResolveBindings(true);
astParser.setBindingsRecovery(true);
astParser.setStatementsRecovery(true);
astParser.setSource(src.toCharArray());
// 6. 创建编辑单元(抽象语法树根节点)
CompilationUnit astRoot = (CompilationUnit) astParser.createAST(null);
// 7. 创建AST重写实例
ASTRewrite astRewrite = ASTRewrite.create(astRoot.getAST());
// 8. 获取旧类名称节点
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
// 9. 创建新类名称节点
SimpleName newName = astRoot.getAST().newSimpleName("Y");
// 10. 使用新名称节点替换老名称节点
astRewrite.replace(oldName, newName, null);
// 11. 创建开关清理访问实例,继承ASTVisitor实例,重写相关的方法实现
SwitchesCleaner switchesFinder = new SwitchesCleaner(astRewrite);
// 12. 将ASTVisitor与AST绑定
astRoot.accept(switchesFinder);
// 13. 编辑单元文本编辑对象,根据AST对象重新源dom
TextEdit edits = astRewrite.rewriteAST(document, compilerOptions);
// 14. 源dom应用编辑使重写动作生效
edits.apply(document);
// 15. 输出修改后代码
System.out.println(document.get());
}
访问AST并重写实现案例
/**
* @author 会灰翔的灰机
* @date 2019/10/18
*/
public class SwitchesCleaner extends ASTVisitor {
// 开关工具使用的代码关键字
private static final String SWITCH_UTILS = "SwitchUtils";
// 重写实例
private ASTRewrite astRewrite;
SwitchesCleaner(ASTRewrite astRewrite) {
this.astRewrite = astRewrite;
}
// 访问所有If语句节点,开关均使用if(true) {} else {}语句处理业务)
@Override
public boolean visit(IfStatement node) {
if (node.getExpression() != null) {
Statement statement = null;
boolean needReplace = false;
// 处理开关为true的场景,例如:if (SwitchUtils.currentCityOpen(switchesNewBywayDegreeCal, order.getCityId()))
if (isSwitchUtilsExpression(node.getExpression())) {
statement = node.getThenStatement();
needReplace = true;
}
// 处理开关为false的场景,例如:if(!SwitchUtils.currentCityOpen(openBywaydegreeLog, order.getCityId()))
if (isSwitchUtilsPrefixExpression(node.getExpression())) {
statement = node.getElseStatement();
needReplace = true;
}
// 替换节点(即删除旧业务代码,保留新业务代码)
if (needReplace) {
astRewrite.replace(node, statement, null);
}
}
return super.visit(node);
}
// 访问所有含中缀表达式,例如:!=,>等等中缀
@Override
public boolean visit(InfixExpression node) {
// 处理开关与其他条件共同判断场景,例如:if (platformId != null && SwitchUtils.currentCityOpen(null, order.getCityId()) && orderType != null)
if (isSwitchUtilsExpression(node.getLeftOperand())) {
astRewrite.replace(node.getLeftOperand(), null, null);
}
if (isSwitchUtilsExpression(node.getRightOperand())) {
astRewrite.replace(node.getRightOperand(), null, null);
}
return super.visit(node);
}
// 访问所有字段声明节点
@Override
public boolean visit(FieldDeclaration node) {
astRewrite.replace(node, null, null);
return super.visit(node);
}
private boolean isSwitchUtilsExpression(Expression expression){
boolean isSwitchExpression = false;
// 方法调用类型的表达式,并且包含关键字
if (expression instanceof MethodInvocation && expression.toString().contains(SWITCH_UTILS)) {
isSwitchExpression = true;
}
return isSwitchExpression;
}
private boolean isSwitchUtilsPrefixExpression(Expression expression){
boolean isSwitchExpression = false;
// 含前缀的表达式,例如:if(!SwitchUtils...
if (expression instanceof PrefixExpression && expression.toString().contains(SWITCH_UTILS)) {
isSwitchExpression = true;
}
return isSwitchExpression;
}
}
总结
JDT是eclipse针对java语言开源的java语法的AST工具,idea也有引用,工具开源且功能强大而又稳定。是我我们这样的简单的语义分析与修改需求的福音啊。使用方法简单,官方文档齐全。总之感谢开源,感谢所有的开源贡献者-,拥抱开源吧23333