优化代码质量的利器、高效清理Java项目中的垃圾类、精确定位无用类,开发者必备利器、加速代码开发与维护、暴打IDEA识别无用类

JavaParser是一种强大的代码分析工具,用户帮助开发者优化代码质量,
通过使用JavaParser,开发者可以轻松的剔除项目中无用类、方法等冗余代码,从而改善代码的可读性和维护性。
JavaParser具有较高的灵活性和准确性,它能够解析java代码,并构建一个标识该代码结构的抽象语法树(AST)。通过对AST的分析,JavaParser可以识别出来未使用的类、方法等,准确地定位到项目中冗余代码。

实现思路

  • 使用静态分析工具(JavaParser)解析代码,构建代码的抽象语法树(AST)
  • 遍历AST,识别所有的类申明和类的引用
  • 建立一个类的引用关系图,记录每个类被哪些其他类引用
  • 对于每个类,检查它是否被其他类引用,如果没有被引用,可以认为它是未被使用

1、添加依赖

2、JavaParser简单介绍

3、代码质量的利器

1、添加依赖

<!-- javaParser -->
<dependency>
  <groupId>com.github.javaparser</groupId>
  <artifactId>javaparser-core</artifactId>
  <version>3.25.8</version>
</dependency>
<!-- hutool -->
<dependency>
	<groupId>org.dromara.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>6.0.0-M10</version>
</dependency>

2、实现思路,JavaParser简单介绍

2.1、构建代码的抽象语法树

// 创建解析代码对象
JavaParser javaParser = new JavaParser();

// 解析分析代码,构建AST
CompilationUnit compilationUnit = javaParser.parse(file).getResult().orElse(null);

2.2、遍历AST识别所有的类申明和类的引用

// 自定义visitor,用户访问所有节点并遍历AST
ClassVisitor visitor = new ClassVisitor(stripFileName);
visitor.visit(compilationUnit, referencedClassesList);

2.3、类的引用关系图

FieldDeclaration表示类中的字段申明和定义的变量,MethodDeclaration表示方法;以下是一个简单的示例代码

// 检查当前类的成员
for (BodyDeclaration < ? > member : n.getMembers()) {
	// 检查当前类中的字段申明和定义的变量如:private final CaptureExceptionService captureExceptionService;
	if (member instanceof FieldDeclaration) {
		FieldDeclaration field = (FieldDeclaration) member;
		// 检查字段的类型
		String fieldType = field.getCommonType().asString();
		if (CharSequenceUtil.equals(fieldType, className)) {
			referencedClasses.add(className);
		}
		else if (CharSequenceUtil.contains(fieldType, className)) {
			// 字段申明和定义的变量可能包含泛型,如:private List< Class > xxxList
			// 【缺失逻辑】:还需要判断referencedName是否被引用,如果该类未被引用则className也未被引用
			String referencedName = n.getName().asString();
			referencedClasses.add(className);
		}
	}
	else if (member instanceof MethodDeclaration) {
		MethodDeclaration method = (MethodDeclaration) member;
		String returnType = method.getType().asString();

		// 返回值匹配对应的类型也算引用【包含泛型】:PageSerializable < CollSimDTO >
		if (CharSequenceUtil.equals(returnType, className)
				|| CharSequenceUtil.contains(returnType, className)) {
			referencedClasses.add(className);
		}

		// 检查方法的参数类型【包含泛型】:PageSerializable < CollSimDTO >
		for (Parameter parameter : method.getParameters()) {
			String paramType = parameter.getTypeAsString();
			if (CharSequenceUtil.equals(paramType, className)
					|| CharSequenceUtil.contains(paramType, className)) {
				referencedClasses.add(className);
			}
		}
		// 检查方法中的对象创建表达式
		method.accept(new ObjectCreationVisitor(className), referencedClasses);
	}
}

2.4、检查它是否被其他类引用

// 输出引用了目标类的其他类
Map < String, Integer > unusedMap = MapUtil.filter(referencedMap, entry -> entry.getValue() == 0);
List < String > unusedClassList = Lists.newArrayList(unusedMap.keySet());
System.out.println("Unused Classes:" + unusedClassList.size());
for (String unusedClass : unusedClassList) {
    System.out.println(unusedClass);
}

3、代码质量的利器

3.1、一键发现无用类

3.1.1 分析未使用类代码如下

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.google.common.collect.Lists;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.date.StopWatch;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.util.ObjUtil;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 分析未使用类
 *
 * @author 管理员
 * @since 2024/2/19
 */
public final class AnalyzerUnusedClassUtil {
    /**
     * 排除目录
     */
    private static final List < String > EXCLUDE_DIRECTORY = Lists.newArrayList("advice", "config", "init",
            "listener", "controller", "impl", "task");

    /**
     * 私有化构造器
     */
    private AnalyzerUnusedClassUtil() {
    }

    /**
     * 分析未使用类
     *
     * @author 管理员
     * @date 2024/2/19
     * @param args 启动参数
     */
    @SuppressWarnings("checkstyle:UncommentedMain")
    public static void main(String[] args) {
        String scanPath = args[0];
        if (CharSequenceUtil.isEmpty(scanPath)) {
            System.out.println("路径为空,扫描结束");
            return;
        }

        File projectDir = new File(scanPath);

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 所有java文件及引用文件
        List < String > fileNameList = listFileNames(projectDir);
        List < File > fileList = FileUtil.loopFiles(projectDir);
        Map < String, Integer > referencedMap = new ConcurrentHashMap <>();

        fileNameList.parallelStream().forEach(fileName -> {
            List < String > referencedClassesList = new ArrayList <>();
            System.out.print("已检查" + referencedMap.size());

            // 遍历项目目录下的所有Java文件
            fileList.forEach(file -> {
                // 如果该类已存在引用,则无须在排查是否存在其他引用
                if (ObjUtil.isNotNull(referencedMap.get(fileName)) && referencedMap.get(fileName) > 0) {
                    // System.out.println(fileName + "已存在引用,检查下一个类");
                    return;
                }

                try {
                    JavaParser javaParser = new JavaParser();
                    ParseResult < CompilationUnit > parseResult = javaParser.parse(file);

                    // 文件解析成功
                    if (parseResult.isSuccessful()) {
                        CompilationUnit compilationUnit = parseResult.getResult()
                                .orElseThrow(() -> new IllegalStateException("Failed to parse file: " + file));
                        String stripFileName = CharSequenceUtil.strip(fileName, ".java");
                        ClassVisitor visitor = new ClassVisitor(stripFileName);
                        visitor.visit(compilationUnit, referencedClassesList);

                        // 如果最后类中的字段申明、方法中还是发现获取当前类所有导包则使用最后杀招循环所有导入
                        if (CollUtil.isEmpty(referencedClassesList)) {
                            NodeList < ImportDeclaration > importList = compilationUnit.getImports();
                            importList.forEach(importClass -> {
                                // 如果导包中存在该类则被使用【注意】:不考虑未清理的无效导包
                                if (CharSequenceUtil.contains(importClass.getNameAsString(), stripFileName)) {
                                    referencedClassesList.add(stripFileName);
                                    return;
                                }
                            });
                        }
                        referencedMap.put(fileName, referencedClassesList.size());
                    }
                    else {
                        // 打印失败文件
                        System.out.println("Parse file: " + file);
                    }
                }
                catch (FileNotFoundException exception) {
                    throw new RuntimeException(exception);
                }
            });
        });

        stopWatch.stop();
        System.out.println();
        System.out.println("获取所有任务的总花费时间(秒):" + stopWatch.getTotalTimeSeconds());

        // 输出引用了目标类的其他类
        Map < String, Integer > unusedMap = MapUtil.filter(referencedMap, entry -> entry.getValue() == 0);
        List < String > unusedClassList = Lists.newArrayList(unusedMap.keySet());
        System.out.println("Unused Classes:" + unusedClassList.size());
        for (String unusedClass : unusedClassList) {
            System.out.println(unusedClass);
        }
    }

    /**
     * 递归获取项目目录下的所有Java文件
     *
     * @author 管理员
     * @date 2024/2/19
     * @param directory 目录
     * @return 所有Java文件
     */
    private static List < String > listFileNames(File directory) {
        List < String > javaFiles = new ArrayList <>();
        File[] files = directory.listFiles();

        // 目录下无对应文件直接返回
        if (files == null) {
            return javaFiles;
        }

        // 遍历文件或目录
        for (File file : files) {
            String fileName = file.getName();

            // 如果是目录并且非排除目录(advice、config、init、listener等),递归获取对应文件
            if (file.isDirectory() && !CollUtil.contains(EXCLUDE_DIRECTORY, fileName)) {
                javaFiles.addAll(listFileNames(file));
            }
            else if (file.isFile() && CharSequenceUtil.endWith(fileName, "java")) {
                // 添加对应java文件
                javaFiles.add(file.getName());
            }
        }
        return javaFiles;
    }

    /**
     * 自定义Visitor,用于遍历AST并记录引用了目标类的其他类
     *
     * @author 管理员
     * @date 2024/2/19
     */
    private static class ClassVisitor extends VoidVisitorAdapter < List < String > > {
        /**
         * 类名
         */
        private final String className;

        /**
         * 构造方法
         *
         * @param className 类名
         */
        ClassVisitor(String className) {
            this.className = className;
        }

        @Override
        public void visit(ClassOrInterfaceDeclaration n, List < String > referencedClasses) {
            // 检查当前类的成员
            for (BodyDeclaration < ? > member : n.getMembers()) {
                // 检查当前类中的字段申明和定义的变量如:private final CaptureExceptionService captureExceptionService;
                if (member instanceof FieldDeclaration) {
                    FieldDeclaration field = (FieldDeclaration) member;
                    // 检查字段的类型
                    String fieldType = field.getCommonType().asString();
                    if (CharSequenceUtil.equals(fieldType, className)) {
                        referencedClasses.add(className);
                    }
                    else if (CharSequenceUtil.contains(fieldType, className)) {
                        // 字段申明和定义的变量可能包含泛型,如:private List< Class > xxxList
                        // 【缺失逻辑】:还需要判断referencedName是否被引用,如果该类未被引用则className也未被引用
                        String referencedName = n.getName().asString();
                        referencedClasses.add(className);
                    }
                }
                else if (member instanceof MethodDeclaration) {
                    MethodDeclaration method = (MethodDeclaration) member;
                    String returnType = method.getType().asString();

                    // 返回值匹配对应的类型也算引用【包含泛型】:PageSerializable < CollSimDTO >
                    if (CharSequenceUtil.equals(returnType, className)
                            || CharSequenceUtil.contains(returnType, className)) {
                        referencedClasses.add(className);
                    }

                    // 检查方法的参数类型【包含泛型】:PageSerializable < CollSimDTO >
                    for (Parameter parameter : method.getParameters()) {
                        String paramType = parameter.getTypeAsString();
                        if (CharSequenceUtil.equals(paramType, className)
                                || CharSequenceUtil.contains(paramType, className)) {
                            referencedClasses.add(className);
                        }
                    }
                    // 检查方法中的对象创建表达式
                    method.accept(new ObjectCreationVisitor(className), referencedClasses);
                }
            }

            super.visit(n, referencedClasses);
        }
    }

    /**
     * 自定义Visitor,用于检查方法中的对象创建表达式
     *
     * @author 管理员
     * @date 2024/2/19
     */
    private static class ObjectCreationVisitor extends VoidVisitorAdapter < List < String > > {
        /**
         * 类名
         */
        private final String className;

        /**
         * 构造方法
         *
         * @param className 类名
         */
        ObjectCreationVisitor(String className) {
            this.className = className;
        }

        @Override
        public void visit(ObjectCreationExpr n, List < String > referencedClasses) {
            String createdType = n.getType().asString();
            if (CharSequenceUtil.equals(createdType, className)) {
                referencedClasses.add(className);
            }
            super.visit(n, referencedClasses);
        }
    }
}

3.1.2、配置Maven插件

把以上编写源码打包成jar包上传私服供所有项目使用

<build>
    <plugins>
       <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>3.0.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>java</goal>
                    </goals>
                </execution>
            </executions>
           <configuration>
               <mainClass>com.hssa.AnalyzerUnusedClassUtil</mainClass>
               <arguments>
                   <argument>${basedir}/src/main/java/com/hssa/shrimp</argument>
               </arguments>
           </configuration>
           <dependencies>
               <dependency>
                   <groupId>com.hssa</groupId>
                   <artifactId>shrimp-paste</artifactId>
                   <version>0.0.1</version>
               </dependency>
           </dependencies>
        </plugin>
    </plugins>
</build>

【说明】shrimp-paste是自行打包在本地伺服中(好处:本地所有项目可使用),也可以把AnalyzerUnusedClassUtil类放工程目录下,这样就不需要引入依赖,编译一下项目工程即可

3.1.3、运行插件

执行 mvn exec:java

在这里插入图片描述

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值