导出 java 类之间的引用关系?

原始需求:
老大让我统计一下 大家修改频率较高的公共类,比如一些常量定义类

目前进展:

  1. 网上搜索到了jdeps, 但是不太会驾驭。查了几个类,代码里import的类数量比打印出来的要多,不明白为啥会缺
  2. 通过jdeps导出的 dot 文件,转成图之后,中间的箭头一大片都是黑的(因为被人引用太多了)…… 无法根据箭头连线看清具体的关系,不方便跟老大对线
  3. 尝试对 jdeps导出的内容直接进行过滤 + 排序 + 统计出现次数,希望通过这个方式查看类被引用的次数。但是跟预期的结果有偏差

求助一下大佬们有没有好的解题思路 谢谢各位

阅读 3.2k
1 个回答

你已经提到查看提交记录来统计修改频率。
至于使用频率,这个肯定要分析整个项目里每一个java文件,统计每个类中字段和方法的使用次数。
你可以分析java文件,构建抽象语法树,然后分析语法树中每一个节点,梳理出调用关系啥的,不过这应该也是个大工程。这个我看到有个java库:javaparser。

一个使用javaparser打印分析结果的例子:

public class Test {
    public static void main(String[] args) {
        
        // Parse the code you want to inspect:
        CompilationUnit cu = StaticJavaParser.parse("import java.lang.String; class X { public void abc(){String abe;abe.toString();} }");

        YamlPrinter printer = new YamlPrinter(true);
        System.out.println(printer.output(cu));
    }
}

或者找现成的工具,比如idea里边,你在代码的字段,方法,变量上右键,有个find usage功能,可以查看字段和方法的引用情况,这个应该就是你想要的,你可以了解一下idea的插件,看有没有提供获取find usage的接口。

jadx中也有类似功能,代码应该是jadx.gui.ui.UsageDialog#performSearch

使用javaparser好像就可以完成,我这个在accessMap中保存了字段和方法被引用的次数。
需要项目源码目录,以及依赖的所有外部jar包目录,外部jar可以先用maven打包,把所有jar整合到一起。

import com.github.javaparser.ParseResult;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserParameterDeclaration;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserVariableDeclaration;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.github.javaparser.utils.SourceRoot;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;


public class ParseProject {
    public static void main(String[] args) throws IOException {

        Path projectRoot = Paths.get("G:\\kaifa_environment\\code\\java\\java-agent");
        String[] roots = new String[]{
                "java-agent-main\\src\\main\\java",
                "java-agent-test\\src\\main\\java",
                /*  "java-parse-main\\src\\main\\java",*/
        };

        Map<String, Integer> accessMap = new HashMap();
        for (String root : roots) {

            Path codeSourceRoot = projectRoot.resolve(root);
            System.out.println(codeSourceRoot);
            SourceRoot sourceRoot = new SourceRoot(codeSourceRoot);


            //项目依赖的所有外部jar路径,以及源码路径
            TypeSolver myTypeSolver = new CombinedTypeSolver(
                    new ReflectionTypeSolver(),
                    new JarTypeSolver("D:\\kaifa_environment\\maven-repository\\org\\javassist\\javassist\\3.28.0-GA\\javassist-3.28.0-GA.jar")
                    , new JarTypeSolver("D:\\kaifa_environment\\maven-repository\\org\\benf\\cfr\\0.152\\cfr-0.152.jar")
                    , new JavaParserTypeSolver(codeSourceRoot)
            );

            JavaSymbolSolver symbolSolver = new JavaSymbolSolver(myTypeSolver);


            ParserConfiguration parserConfiguration = new ParserConfiguration();
            parserConfiguration.setSymbolResolver(symbolSolver);


            try {
                sourceRoot.getParserConfiguration().setSymbolResolver(symbolSolver);
                List<ParseResult<CompilationUnit>> parseResults = sourceRoot.tryToParse();
                for (ParseResult<CompilationUnit> parseResult : parseResults) {
                    parseResult.ifSuccessful(cu -> {

                        List<String> methodCall = getMethodCall(cu);
                     //   System.out.println("方法:");
                       // System.out.println(methodCall);

                        List<String> fieldCall = getFieldCall(cu);
                   //     System.out.println("字段:");
                      //  System.out.println(fieldCall);

                       methodCall.addAll(fieldCall);

                        for (String qualifiedName : methodCall) {
                            Integer count = accessMap.getOrDefault(qualifiedName, Integer.valueOf(0));
                            accessMap.put(qualifiedName, count + 1);
                        }


                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println(accessMap.size());


    }


    public static List<String> getMethodCall(CompilationUnit cu) {
        List<String> methodCallExprs = new ArrayList<>();
        cu.findAll(MethodCallExpr.class).forEach(mce -> {
            methodCallExprs.add(mce.resolve().getQualifiedSignature());
        });

        return methodCallExprs;
    }


    public static List<String> getFieldCall(CompilationUnit cu) {
        List<String> fieldAccess = new ArrayList<>();

        //获取字段访问情况
        cu.findAll(FieldAccessExpr.class).forEach(new Consumer<FieldAccessExpr>() {
            @Override
            public void accept(FieldAccessExpr mce) {
                try {
                    ResolvedValueDeclaration resolve = mce.resolve();
                    String fieldPath = getQualifiedName(resolve, mce.getNameAsString());
                    fieldAccess.add(fieldPath);
                } catch (Exception e) {
                }

            }
        });


        // 名称访问情况
        cu.findAll(NameExpr.class).forEach(mce -> {
            try {
                ResolvedValueDeclaration resolve = mce.resolve();
                //System.out.println(mce.getName());
                String fieldPath = getQualifiedName(resolve, mce.getNameAsString());
                if (fieldPath.length() > 0) {
                    fieldAccess.add(fieldPath);
                }

            } catch (Exception e) {
            }
        });
        return fieldAccess;
    }

    public static String getQualifiedName(ResolvedValueDeclaration resolve, String fieldName) {
        String fieldPath = "";

/*        if (resolve instanceof JavaParserFieldDeclaration) {
            JavaParserFieldDeclaration javaParserFieldDeclaration = (JavaParserFieldDeclaration) resolve;
            FieldDeclaration wrappedNode = javaParserFieldDeclaration.getWrappedNode();
            Node parentNode = wrappedNode.getParentNode().get();
            if (parentNode instanceof ClassOrInterfaceDeclaration) {
                ClassOrInterfaceDeclaration typeDeclaration = (ClassOrInterfaceDeclaration) parentNode;
                //获取typeDeclaration的qualifiedName
                fieldPath = typeDeclaration.getFullyQualifiedName().get() + "." + fieldName;
            }
        } else if (resolve instanceof ReflectionFieldDeclaration) {
            ReflectionFieldDeclaration reflectionFieldDeclaration = (ReflectionFieldDeclaration) resolve;
            ResolvedTypeDeclaration resolvedTypeDeclaration = reflectionFieldDeclaration.declaringType();//获取字段的类型
            fieldPath = resolvedTypeDeclaration.getQualifiedName() + "." + fieldName;
        } else if (resolve instanceof JavassistFieldDeclaration){
            ResolvedFieldDeclaration javassistFieldDeclaration = (ResolvedFieldDeclaration) resolve;
            fieldPath = javassistFieldDeclaration.declaringType().getQualifiedName() + "." + fieldName;
        }*/


        if (resolve instanceof ResolvedFieldDeclaration) {
            ResolvedFieldDeclaration resolvedValueDeclaration = (ResolvedFieldDeclaration) resolve;
            fieldPath = resolvedValueDeclaration.declaringType().getQualifiedName() + "." + fieldName;
        } else if (resolve instanceof ResolvedEnumConstantDeclaration) {
            ResolvedType type = resolve.getType();
            String qualifiedName = ((ReferenceTypeImpl) type).getTypeDeclaration().get().getQualifiedName();
            fieldPath = qualifiedName + "." + fieldName;
        } else if (resolve instanceof JavaParserParameterDeclaration) {
            //System.out.println("函数入参:"+resolve.getName());
        } else if (resolve instanceof JavaParserVariableDeclaration) {
            //System.out.println("变量引用:"+resolve.getName());
        } else {
            System.out.println("没解析的类型");
        }
        return fieldPath;
    }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题