java-callgraph2 项目用于对 Java 代码(编译后的 class、jar、war、jmod 等文件)进行静态分析,支持输出的文件见 生成文件说明
当前项目原本 fork 自 https://github.com/gousiosg/java-callgraph,用于生成 Java 方法调用关系
后来进行了优化和增强,差别已比较大,不容易合并回原始项目中,且仅提供通过静态分析获取 Java 方法调用关系的功能,因此创建了该项目
当前项目只会输出静态分析结果到文件,不会写入数据库;假如需要将结果写入数据库进行后续分析,例如生成 Java 代码完整方法调用链、生成调用堆栈、JarDiff 分析 jar 文件方法修改影响范围等,可使用项目 https://github.com/Adrninistrator/java-all-call-graph
当前项目提供了插件功能,可用于为 Java 代码自动生成 UML 时序图(文档未完成,暂未提交),可参考 https://github.com/Adrninistrator/gen-java-code-uml-sequence-diagram。
提问不需要注册,但不再检索项目的最新内容
https://deepwiki.com/Adrninistrator/java-callgraph2
通过大模型分析项目代码,可向大模型提出关于项目的问题,包括使用方法等
提问需要注册
https://zread.ai/Adrninistrator/java-callgraph2
作用同上
原始 java-callgraph 在多数场景下能够获取到 Java 方法调用关系,但以下场景的调用关系会缺失
- 接口与实现类方法
假如存在接口 Interface1,及其实现类 Impl1,若在某个类 Class1 中引入了接口 Interface1,实际为实现类 Impl1 的实例(使用 Spring 时的常见场景),在其方法 Class1.func1() 中调用了 Interface1.fi() 方法;
原始 java-callgraph 生成的方法调用关系中,只包含 Class1.func1() 调用 Interface1.fi() 的关系,Class1.func1() 调用 Impl1.fi(),及 Impl1.fi() 向下调用的关系会缺失。
- Runnable 实现类线程调用
假如 f1() 方法中使用内部匿名类形式的 Runnable 实现类在线程中执行操作,在线程中执行了 f2() 方法,如下所示
private void f1() {
new Thread(new Runnable() {
@Override
public void run() {
f2();
}
}).start();
}原始 java-callgraph 生成的方法调用关系中,f1() 调用 f2(),及 f2() 向下调用的关系会缺失;
对于使用命名类形式的 Runnable 实现类在线程中执行操作的情况,存在相同的问题,原方法调用线程中执行的方法,及继续向下的调用关系会缺失。
- Callable 实现类线程调用
与 Runnable 实现类线程调用情况类似,略。
- Thread 子类线程调用
与 Runnable 实现类线程调用情况类似,略。
- lambda 表达式(含线程调用等)
假如 f1() 方法中使用 lambda 表达式的形式在线程中执行操作,在线程中执行了 f2() 方法,如下所示
private void f1() {
new Thread(() -> f2()).start();
}原始 java-callgraph 生成的方法调用关系中,f1() 调用 f2(),及 f2() 向下调用的关系会缺失;
对于其他使用 lambda 表达式的情况,存在相同的问题,原方法调用 lambda 表达式中执行的方法,及继续向下的调用关系会缺失。
- Stream 调用
在使用 Stream 时,通过 xxx::func 方式调用方法,原始 java-callgraph 生成的方法调用关系中会缺失。如以下示例中,当前方法调用当前类的 map2()、filter2(),及 TestDto1 类的 getStr() 方法的调用关系会缺失。
list.stream().map(this::map2).filter(this::filter2).collect(Collectors.toList());
list.stream().map(TestDto1::getStr).collect(Collectors.toList());- 父类调用子类的实现方法
假如存在抽象父类 Abstract1,及其非抽象子类 ChildImpl1,若在某个类 Class1 中引入了抽象父类 Abstract1,实际为子类 ChildImpl1 的实例(使用 Spring 时的常见场景),在其方法 Class1.func1() 中调用了 Abstract1.fa() 方法;
原始 java-callgraph 生成的方法调用关系中,只包含 Class1.func1() 调用 Abstract1.fa() 的关系,Class1.func1() 调用 ChildImpl1.fa() 的关系会缺失。
- 子类调用父类的实现方法
假如存在抽象父类 Abstract1,及其非抽象子类 ChildImpl1,若在 ChildImpl1.fc1() 方法中调用了父类 Abstract1 实现的方法 fi();
原始 java-callgraph 生成的方法调用关系中,ChildImpl1.fc1() 调用 Abstract1.fi() 的关系会缺失。
针对以上问题,java-callgraph2 都进行了优化,能够生成缺失的调用关系。
对于更复杂的情况,例如存在接口 Interface1,及其抽象实现类 Abstract1,及其子类 ChildImpl1,若在某个类中引入了抽象实现类 Abstract1 并调用其方法的情况,生成的方法调用关系中也不会出现缺失。
当前项目在对 Java 代码进行静态分析时,主要的步骤如下
处理需要解析的文件
解析合并后的或原始 jar 文件
遍历并解析 jar 文件中的类
解析结果输出到文件
支持对以下文件进行解析
| 文件类型 | 说明 |
|---|---|
| class | 在解析时需要指定 class 文件所在的目录 |
| jar | 支持解析 jar 文件中的 class 等文件,及 jar 文件中的 jar 文件(Spring Boot 编译生成的 jar)中的文件 |
| war | 支持解析 war 文件中的 class 等文件,及 war 文件中的 jar 文件中的文件 |
| jmod | JDK9 及以上的 JDK 标准库文件格式 |
| xml | 支持解析 Spring、MyBatis 相关的 XML 数据(需要通过 java-all-call-graph 实现) |
| properties | 解析 properties 文件内容 |
假如指定需要解析的文件是指定了一个 jar 文件,且该 jar 文件中不再存在 jar 文件,则会直接使用原始 jar 文件进行解析
- 需要合并的情况
在以下情况下,会将指定需要解析的文件、目录(中的文件)合并为一个 jar 文件
指定的需要解析的文件多于一个
指定的需要解析的内容包含目录
指定的需要解析的是 war 文件且其中包含 jar 文件
指定的需要解析的是 jar 文件且其中包含 jar 文件
指定的需要解析的是 jmod 文件
- 合并生成的 jar 文件保存目录
若指定的需要解析的第一个元素为 jar、war、jmod 文件,则新生成的 jar 文件生成在同一个目录中
若指定的需要解析的第一个元素为目录,则新生成的 jar 文件生成在该目录中
文件名在第一个元素名称之后增加“-javacg2_merged.jar”作为后缀
- 合并生成的 jar 文件结构
合并生成的 jar 文件的第一层目录名为格式为“{序号}@{原 jar、war、jmod 文件或目录名}”,如“[email protected]”
假如需要解析的 jar 文件包含 multi-release JAR,则需要在_javacg2_config/config.properties 配置文件中指定参数 jdk.runtime.major.version,在合并 multi-release JAR 时使用指定版本的 class 文件,即“META-INF/versions/{JDK 主版本号}”目录中的 class 文件
具体文件格式见 输出文件格式 ,有部分文件需要使用 java-all-call-graph 组件时支持输出
生成的方法调用关系文件中的该当调用类型可参考 方法调用类型
需要使用 JDK8 及以上版本
若需要通过 IDE 打开项目源码运行,建议安装 Gradle 管理依赖库
通过配置文件指定
通过代码指定
以上两种方式的效果是相同的,每个配置文件及配置参数在代码中都存在对应项
支持以下三种格式的配置参数
当前项目中各个参数的对应枚举为 com.adrninistrator.javacg2.conf.enums.JavaCG2ConfigKeyEnum
每个枚举常量代表一个参数,对应的配置文件都是 _javacg2_config/config.properties
参数为键值对形式,每个参数指定唯一的值
当前项目中的对应枚举为 com.adrninistrator.javacg2.conf.enums.JavaCG2OtherConfigFileUseListEnum
每个枚举常量代表一个参数,对应一个配置文件
参数值区分顺序,可指定多个值
当前项目中的对应枚举为 com.adrninistrator.javacg2.conf.enums.JavaCG2OtherConfigFileUseSetEnum
每个枚举常量代表一个参数,对应一个配置文件
参数值不区分顺序,可指定多个值
当前项目中的对应枚举为 com.adrninistrator.javacg2.el.enums.JavaCG2ElConfigEnum
每个枚举常量代表一个参数,对应一个配置文件
参数值需要指定 EL 表达式,整个参数值是一个表达式
参考 配置参数示例
java-callgraph2 需要使用的重要配置参数是配置文件 _javacg2_config/jar_dir.properties
用于指定需要解析的 jar、war、jmod 文件路径,或保存 class、jar、war、jmod 文件的目录路径
参考 表达式使用通用说明文档
参考 表达式字符串比较说明文档
在代码中使用 com.adrninistrator.javacg2.conf.JavaCG2ConfigureWrapper 类可以指定配置参数
在创建 com.adrninistrator.javacg2.entry.JavaCG2Entry 类实例时需要有参数的构造函数“JavaCG2Entry(JavaCG2ConfigureWrapper javaCG2ConfigureWrapper)”
以下为 JavaCG2ConfigureWrapper 用于指定配置参数的方法
| 方法名称 | 方法作用 |
|---|---|
| setMainConfig | 设置 key value 形式的参数 |
| setOtherConfigList | 设置区分顺序的参数 |
| setOtherConfigSet | 设置不区分顺序的参数 |
| setElConfigText | 设置 EL 表达式 |
执行以下类可对指定的 jar 文件等进行静态分析
com.adrninistrator.javacg2.entry.JavaCG2Entry
通过 IDE 打开项目源码运行
在其他项目中引用当前项目的库运行
使用项目源码构建后运行
| 运行方式 | 支持的参数配置方式 |
|---|---|
| 通过 IDE 打开项目源码运行 | 通过配置文件指定 通过代码指定 |
| 在其他项目中引用当前项目的库运行 | 通过配置文件指定 通过代码指定 |
| 使用项目源码构建后运行 | 通过配置文件指定 |
通过 IDE 打开当前项目,由 Gradle 管理依赖库,可使用源码运行
假如需要通过配置文件指定参数,可修改项目中的配置文件相关,再运行项目入口类
项目运行模块需要选择 test,以使 test 模块中的 log4j2 配置文件生效
假如需要通过代码指定参数,可直接执行后续说明的示例方法,或者参考示例方法进行修改
在其他的项目中,使用 Maven/Gradle 等管理依赖库,并添加对当前项目的依赖
- 使用 Maven 管理依赖
<dependency>
<groupId>com.github.adrninistrator</groupId>
<artifactId>java-callgraph2</artifactId>
<version>版本号</version>
<type>pom</type>
</dependency>- 使用 Gradle 管理依赖
implementation("com.github.adrninistrator:java-callgraph2: 版本号")
最新版本号可查看 https://mvnrepository.com/artifact/com.github.adrninistrator/java-callgraph2
假如需要通过配置文件指定参数,则需要将 java-callgraph2 项目的 src/main/resources 目录中以_javacg2_开头的目录复制到其他项目的 src/main/resources 或 src/test/resources 目录
修改配置文件相关参数后,可运行入口类
与“通过 IDE 打开项目源码运行”的说明相同
在项目根目录执行以下命令
gradlew gen_run_jar
构建完成后,会在项目根目录 jar_output_dir 生成相关目录及文件
| 目录、文件名 | 作用 |
|---|---|
| _el_example | 表达式相关的示例与说明文件 |
| _javacg2_xxx | 当前项目使用的配置文件保存目录 |
| config | log4j2 配置文件保存目录 |
| jar | 当前项目编译生成的 jar 文件保存目录 |
| lib | 当前项目的依赖库 jar 文件 |
| log_javacg2 | 保存日志文件目录,运行后会生成 |
| run.bat | 用于执行当前项目解析 Java 代码的脚本 |
| run.sh | 用于执行当前项目解析 Java 代码的脚本 |
对配置文件参数进行配置后,可执行 run.bat 或 run.sh 脚本,调用项目的入口类,解析指定的 Java 代码
在项目根目录执行以下命令,可生成用于解析的示例 jar 文件 build/test.jar
gradlew test_gen_jar参考示例方法 test.parse.TestParse:testParseJavaCG2TestLib,会对以上生成的示例 jar 文件进行解析
示例方法代码如下:
JavaCG2ConfigureWrapper javaCG2ConfigureWrapper = new JavaCG2ConfigureWrapper();
javaCG2ConfigureWrapper.setOtherConfigList(JavaCG2OtherConfigFileUseListEnum.OCFULE_JAR_DIR, "build/test.jar");
JavaCG2Entry javaCG2Entry = new JavaCG2Entry(javaCG2ConfigureWrapper);
Assert.assertTrue(javaCG2Entry.run());