自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java方法完整調(diào)用鏈生成工具

開發(fā) 后端
本文實(shí)現(xiàn)了一個(gè)工具,能夠批量生成指定Java方法向下的完整調(diào)用鏈,對(duì)于關(guān)注的Java方法,能夠生成其向下調(diào)用的方法信息,及被調(diào)用方法再向下調(diào)用的方法,直到最下層被調(diào)用的方法。

1. 前言

在很多場(chǎng)景下,如果能夠生成Java代碼中方法之間的調(diào)用鏈,是很有幫助的,在代碼審計(jì)及漏洞分析等場(chǎng)景中也是。

IDEA提供了顯示調(diào)用指定Java方法向上的完整調(diào)用鏈的功能,可以通過“Navigate -> Call Hierarchy”菜單(快捷鍵:Ctrl+Alt+H)使用;Eclipse也提供了相同的功能。但以上都需要針對(duì)每個(gè)方法進(jìn)行手工處理,拷貝出來的文本無法展示調(diào)用層級(jí),且不支持生成指定Java方法向下的完整調(diào)用鏈。

以下實(shí)現(xiàn)了一個(gè)工具,能夠批量生成指定Java方法向下的完整調(diào)用鏈,對(duì)于關(guān)注的Java方法,能夠生成其向下調(diào)用的方法信息,及被調(diào)用方法再向下調(diào)用的方法,直到最下層被調(diào)用的方法。

也可以生成調(diào)用指定Java類向上的完整調(diào)用鏈,對(duì)于關(guān)注的Java類的方法,能夠生成調(diào)用對(duì)應(yīng)方法的方法信息,及調(diào)用上述方法的信息,直到最上層未被其他方法調(diào)用的方法(通常是對(duì)外提供的服務(wù),或定時(shí)任務(wù)等)。

2. 輸出結(jié)果示例

2.1. 調(diào)用指定類向上的完整調(diào)用鏈?zhǔn)纠?/h3>

調(diào)用指定類向上的完整調(diào)用鏈輸出結(jié)果格式類似一棵樹,每行代表一個(gè)Java方法,與實(shí)際的代碼執(zhí)行順序無關(guān),前面的數(shù)字越大代表調(diào)用層級(jí)越靠上,0代表指定類中的方法。

對(duì)于不被其他方法調(diào)用的方法,認(rèn)為是入口方法,在對(duì)應(yīng)行的最后會(huì)顯示“!entry!”。

當(dāng)存在上述調(diào)用關(guān)系時(shí),生成的調(diào)用指定類向上的完整調(diào)用鏈如下所示:

  1. [0]#DestClass.destfunc() 
  2. [1]#  ClassA3.funcA3() 
  3. [2]#    ClassA2.funcA2() 
  4. [3]#      ClassA1.funcA1()  !entry! 
  5. [1]#  ClassB1.funcB1()  !entry! 
  6. [1]#  ClassC2.funcC2() 
  7. [2]#    ClassC1.funcC1()    !entry! 

以下為使用該工具生成的調(diào)用Mybatis的SqlSessionUtils類的部分方法向上完整調(diào)用鏈(方法參數(shù)太長,已省略):

2.2. 指定方法向下完整調(diào)用鏈?zhǔn)纠?/h3>

指定方法向下完整調(diào)用鏈輸出結(jié)果類似一棵樹,每行代表一個(gè)Java方法,與實(shí)際的代碼執(zhí)行順序一致,前面的數(shù)字越大代表調(diào)用層級(jí)越靠下,0代表指定方法。

當(dāng)存在上述調(diào)用關(guān)系時(shí),生成的指定方法向下完整調(diào)用鏈如下所示:

  1. [0]#DestClass.destfunc() 
  2. [1]#  ClassA1.funcA1() 
  3. [2]#    ClassA2a.funcA2a() 
  4. [2]#    ClassA2b.funcA2b() 
  5. [3]#      ClassA3.funcA3() 
  6. [1]#  ClassB1.funcB1() 
  7. [1]#  ClassC1.funcC1() 
  8. [2]#    ClassC2.funcC2() 

以下為使用該工具生成的Mybatis的SqlSessionFactoryBean:scanClasses()方法向下的完整調(diào)用鏈:

除此之外,當(dāng)方法指定了注解時(shí),也可以顯示在結(jié)果中;當(dāng)出現(xiàn)方法循環(huán)調(diào)用時(shí),會(huì)顯示出現(xiàn)循環(huán)調(diào)用的方法。

3. 適用場(chǎng)景

3.1. 分析代碼執(zhí)行流程

使用該工具生成指定方法向下調(diào)用鏈的功能,可以將代碼中復(fù)雜的方法調(diào)用轉(zhuǎn)換為相對(duì)簡單的方法調(diào)用鏈形式展示。

人工查看生成的調(diào)用鏈時(shí),能夠通過類名及方法名識(shí)別出對(duì)應(yīng)含義。

支持將不關(guān)注的方法調(diào)用忽略,僅展示重要的方法調(diào)用。

對(duì)于分析代碼執(zhí)行流程有一定幫助,適合進(jìn)行代碼審計(jì)時(shí)梳理交易流程、查找敏感API調(diào)用等場(chǎng)景。

3.2. 確認(rèn)被修改代碼的影響范圍

使用該工具生成指定方法向上調(diào)用鏈的功能,可以生成調(diào)用指定類的所有方法的調(diào)用鏈。

能識(shí)別入口方法,減少人工逐層確認(rèn)入口方法的工作量。

可用于快速確認(rèn)被修改代碼的影響范圍。

3.3. 應(yīng)用功能拆分

在進(jìn)行應(yīng)用功能拆分時(shí),需要準(zhǔn)確定位指定功能涉及的數(shù)據(jù)庫表,及使用了對(duì)應(yīng)數(shù)據(jù)庫表的相關(guān)入口方法。

使用該工具生成指定方法向下調(diào)用鏈的功能,生成指定入口方法向下的調(diào)用鏈,能夠根據(jù)類的包名快速找到Mapper接口(使用Mybatis的場(chǎng)景),即可找到相關(guān)的數(shù)據(jù)庫表。

使用該工具生成指定方法向上調(diào)用鏈的功能,生成調(diào)用指定Mapper接口向上的調(diào)用鏈,能夠根據(jù)“!entry!”找到入口方法。

重復(fù)執(zhí)行以上過程,直到?jīng)]有再找到新的Mapper接口(即數(shù)據(jù)庫表)和入口方法,即可確認(rèn)指定功能涉及的數(shù)據(jù)庫表及相關(guān)入口方法。

4. 使用說明

4.1. 依賴環(huán)境

該工具將Java方法調(diào)用關(guān)系寫入文件之后,會(huì)將數(shù)據(jù)保存在數(shù)據(jù)庫中,需要訪問MySQL數(shù)據(jù)庫(理論上支持其他數(shù)據(jù)庫,但需要對(duì)SQL語句進(jìn)行調(diào)整)。

所使用的數(shù)據(jù)庫用戶需要有DML讀寫權(quán)限,及DDL權(quán)限(需要執(zhí)行CREATE TABLE、TRUNCATE TABLE操作)。

4.2. 引入組件

在使用該工具前,首先需要在對(duì)應(yīng)的項(xiàng)目引入該工具組件的依賴,將其引入到test模塊或使用provided類型,可以避免發(fā)布到服務(wù)器中。

Gradle

  1. testImplementation 'com.github.adrninistrator:java-all-call-graph:0.0.8' 

Maven

  1. <dependency> 
  2.   <groupId>com.github.adrninistrator</groupId> 
  3.   <artifactId>java-all-call-graph</artifactId> 
  4.   <version>0.0.8</version> 
  5.   <type>provided</type> 
  6. </dependency> 

最新版本號(hào)可查看 https://search.maven.org/artifact/com.github.adrninistrator/java-all-call-graph 。

對(duì)應(yīng)代碼地址為 https://github.com/Adrninistrator/java-all-call-graph 。

建議在需要生成方法調(diào)用鏈的項(xiàng)目中分別引入依賴,可以使每個(gè)項(xiàng)目使用單獨(dú)的配置,不會(huì)相互影響。

該工具僅引入了log4j-over-slf4j組件,在引入該工具組件的項(xiàng)目中,還需要引入log4j2、logback等日志組件,且保證配置正確,能夠在本地正常運(yùn)行。

4.3. 執(zhí)行步驟

4.3.1. 總體步驟

該工具的總體使用步驟如下:

a. 將后續(xù)步驟使用的幾個(gè)啟動(dòng)類對(duì)應(yīng)的Java文件,及配置文件解壓到當(dāng)前Java項(xiàng)目的test模塊的對(duì)應(yīng)目錄中,該步驟只需要執(zhí)行一次;

b. 調(diào)用增強(qiáng)后的java-callgraph.jar(詳細(xì)內(nèi)容見后續(xù)“原理說明”部分),解析指定jar包中的class文件,將Java方法調(diào)用關(guān)系寫入文件;從該文件讀取Java方法調(diào)用關(guān)系,再寫入MySQL數(shù)據(jù)庫;

c.1 需要生成調(diào)用指定類的向上完整方法調(diào)用鏈時(shí),從數(shù)據(jù)庫讀取方法調(diào)用關(guān)系,再將完整的方法調(diào)用鏈寫入文件;

c.2 需要生成指定方法的向下完整方法調(diào)用鏈時(shí),從數(shù)據(jù)庫讀取方法調(diào)用關(guān)系,再將完整的方法調(diào)用鏈寫入文件;

如下圖所示:

4.3.2. 釋放啟動(dòng)類及配置文件

當(dāng)前步驟在每個(gè)Java項(xiàng)目只需要執(zhí)行一次。

執(zhí)行當(dāng)前步驟時(shí),需要執(zhí)行main()方法的類名如下:

  1. com.adrninistrator.jacg.unzip.UnzipFile 

需要選擇classpath對(duì)應(yīng)模塊為test。

執(zhí)行以上類后,會(huì)將java-all-callgraph.jar中保存配置文件的~jacg_config、~jacg_sql目錄,保存啟動(dòng)類的“test/jacg”目錄,分別釋放到當(dāng)前Java項(xiàng)目的test模塊的resources、java目錄中(僅在本地生效,避免發(fā)布到服務(wù)器中)。

若當(dāng)前Java項(xiàng)目存在“src/test”或“src/unit.test”目錄,則將配置文件與Java文件分別釋放在該目錄的resources、java目錄中;

若當(dāng)前Java項(xiàng)目不存在以上目錄,則將上述文件釋放在“~jacg-[當(dāng)前時(shí)間戳]”目錄中,之后需要手工處理,將對(duì)應(yīng)目錄拷貝至test模塊對(duì)應(yīng)目錄中。

4.3.3. Java方法調(diào)用關(guān)系入庫

在生成Java方法調(diào)用關(guān)系并寫入數(shù)據(jù)庫之前,需要確保需要分析的jar包或war包已存在,對(duì)于通過源碼使用構(gòu)建工具生成的jar/war包,或者M(jìn)aven倉庫中的jar包(需要是包含.class文件的jar包),均可支持。

當(dāng)需要解析的jar/war包中的class文件內(nèi)容發(fā)生變化時(shí),需要重新執(zhí)行當(dāng)前步驟,以重新獲取對(duì)應(yīng)jar/war包中的Java方法調(diào)用關(guān)系,寫入文件及數(shù)據(jù)庫;若需要解析的jar/war包文件未發(fā)生變化,則不需要重新執(zhí)行當(dāng)前步驟。

執(zhí)行當(dāng)前步驟時(shí),需要執(zhí)行main()方法的類名如下:

  1. test.jacg.TestRunnerWriteDb 

需要選擇classpath對(duì)應(yīng)模塊為test。

當(dāng)前步驟執(zhí)行的操作及使用的相關(guān)參數(shù)如下圖所示:

b.1 調(diào)用增強(qiáng)后的java-callgraph.jar中的類的方法

TestRunnerWriteDb類讀取配置文件 config.properties 中的參數(shù):

call.graph.jar.list :等待解析的jar包路徑列表,各jar包路徑之間使用空格分隔(若路徑中包含空格,則需要使用""包含對(duì)應(yīng)的路徑)

將第1個(gè)jar包路徑后面加上“.txt”作為本次保存Java方法調(diào)用關(guān)系文件路徑;

設(shè)置JVM參數(shù)“output.file”值為本次保存Java方法調(diào)用關(guān)系文件的路徑,調(diào)用增強(qiáng)后的java-callgraph.jar中的類的方法,通過方法的參數(shù)傳遞上述jar包路徑列表;

b.2 解析指定jar包

增強(qiáng)后的java-callgraph.jar中的類的方法開始解析指定的jar包;

b.3 將Java方法調(diào)用關(guān)系寫入文件

增強(qiáng)后的java-callgraph.jar中的類的方法將解析出的Java方法調(diào)用關(guān)系寫入指定的文件中;

b.4 讀取Java方法調(diào)用關(guān)系文件

TestRunnerWriteDb類讀取保存Java方法調(diào)用關(guān)系的文件,文件路徑即第1個(gè)jar包路徑加“.txt”;

b.5 將Java方法調(diào)用關(guān)系寫入數(shù)據(jù)庫

TestRunnerWriteDb類讀取配置文件 i_allowed_class_prefix.properties ,該文件中指定了需要處理的類名前綴,可指定包名,或包名+類名,示例如下:

  1. com.test 
  2. com.test.Test1 

讀取配置文件 config.properties 中的參數(shù):

app.name :當(dāng)前應(yīng)用名稱,對(duì)應(yīng)數(shù)據(jù)庫表名后綴,該參數(shù)值中的分隔符不能使用-,需要使用_

thread.num :寫入數(shù)據(jù)庫時(shí)并發(fā)處理的線程數(shù)量,也是數(shù)據(jù)源連接池?cái)?shù)量

db.driver.name :數(shù)據(jù)庫驅(qū)動(dòng)類名

db.url :數(shù)據(jù)庫URL,使用MySQL時(shí),url需要指定rewriteBatchedStatements=true,開啟批量插入,提高效率

db.username :數(shù)據(jù)庫用戶名

db.password :數(shù)據(jù)庫密碼

input.ignore.other.package :忽略其他包的開關(guān),值為true/false;當(dāng)開關(guān)為開時(shí),僅將 i_allowed_class_prefix.properties 中指定的類名前綴相符的類調(diào)用關(guān)系寫入數(shù)據(jù)庫;當(dāng)開關(guān)為關(guān)時(shí),所有的類調(diào)用關(guān)系都寫入數(shù)據(jù)庫

向數(shù)據(jù)庫寫入數(shù)據(jù)庫前,會(huì)判斷對(duì)應(yīng)數(shù)據(jù)庫表是否存在,若不存在則創(chuàng)建,之后會(huì)執(zhí)行“TRUNCATE TABLE”操作清空表中的數(shù)據(jù);

根據(jù)配置文件 config.properties 中的 input.ignore.other.package 參數(shù)值及配置文件 i_allowed_class_prefix.properties ,將Java方法調(diào)用關(guān)系逐條寫入數(shù)據(jù)庫中;

增強(qiáng)后的java-callgraph.jar除了會(huì)將Java方法調(diào)用關(guān)系寫入文件外,還會(huì)將各個(gè)方法上的注解信息寫入文件(文件名為保存方法調(diào)用關(guān)系的文件名加上“-annotation.txt”);TestRunnerWriteDb類也會(huì)讀取對(duì)應(yīng)文件,將各方法上的注解信息寫入數(shù)據(jù)庫中。

4.3.4. 生成調(diào)用指定類向上的完整調(diào)用鏈

執(zhí)行當(dāng)前步驟之前,需要確認(rèn)Java方法調(diào)用關(guān)系已成功寫入數(shù)據(jù)庫中。

執(zhí)行當(dāng)前步驟時(shí),需要執(zhí)行main()方法的類名如下:

  1. test.jacg.TestRunnerGenAllGraph4Callee 

需要選擇classpath對(duì)應(yīng)模塊為test。

當(dāng)前步驟執(zhí)行的操作及使用的相關(guān)參數(shù)如下圖所示:

c.1.1 從數(shù)據(jù)庫讀取Java方法調(diào)用關(guān)系

TestRunnerGenAllGraph4Callee類讀取配置文件 o_g4callee_class_name.properties ,該文件中指定了需要生成向上完整調(diào)用鏈的類名;若存在同名類,則類名需要指定完整類名;若不存在同名類,則類名需要指定簡單類名;示例如下:

  1. Test1 
  2. com.test.Test1 

讀取配置文件 config.properties 中的參數(shù):

thread.num :從數(shù)據(jù)庫并發(fā)讀取數(shù)據(jù)的線程數(shù)量,也是數(shù)據(jù)源連接池?cái)?shù)量;若 o_g4callee_class_name.properties 配置文件中的記錄數(shù)比該值小,則會(huì)使用記錄數(shù)覆蓋該參數(shù)值

以下參數(shù)說明略:app.name、db.driver.name、db.url、db.username、db.password

c.1.2 將方法完整調(diào)用鏈(向上)寫入文件

對(duì)于配置文件 o_g4callee_class_name.properties 中指定的類,對(duì)每個(gè)類生成一個(gè)對(duì)應(yīng)的文件,文件名為“[類名].txt”,在某個(gè)類對(duì)應(yīng)的文件中,會(huì)為對(duì)應(yīng)類的每個(gè)方法生成向上完整調(diào)用鏈;

以上文件名示例為“TestClass1.txt”;

每次執(zhí)行時(shí)會(huì)生成一個(gè)新的目錄,用于保存輸出文件,目錄名格式為“~jacg_output_for_callee/[yyyyMMdd-HHmmss.SSS]”;

讀取配置文件 config.properties 中的參數(shù):

call.graph.output.detail :輸出文件中調(diào)用關(guān)系的詳細(xì)程度,1: 最詳細(xì),包含完整類名+方法名+方法參數(shù),2: 中等,包含完整類名+方法名,3: 最簡單,包含簡單類名(對(duì)于同名類展示完整類名)+方法名,示例如下

call.graph.output.detail參數(shù)值 顯示示例
1 com.test.Test1.func1(java.lang.String)
2 com.test.Test1.func1
3 Test1.func1

show.method.annotation :調(diào)用鏈中是否顯示方法上的注解開關(guān),值為true/false;當(dāng)開關(guān)為開時(shí),會(huì)顯示當(dāng)前方法上的全部注解的完整類名,格式為“[方法信息]@注解1@注解2...”

gen.combined.output :是否生成調(diào)用鏈的合并文件開關(guān),值為true/false;當(dāng)開關(guān)為開時(shí),在為各個(gè)類生成了對(duì)應(yīng)的調(diào)用鏈文件后,會(huì)生成一個(gè)將全部文件合并的文件,文件名為“~all-4callee.txt”

gen.upwards.methods.file :生成向上的調(diào)用鏈時(shí),是否需要為每個(gè)方法生成單獨(dú)的文件開關(guān),值為true/false;當(dāng)開關(guān)為開時(shí),會(huì)為o_g4callee_class_name.properties中指定的每個(gè)類的每個(gè)方法單獨(dú)生成一個(gè)文件,保存在“~jacg_output_for_callee/[yyyyMMdd-HHmmss.SSS]/methods”

4.3.5. 生成指定方法向下完整調(diào)用鏈

執(zhí)行當(dāng)前步驟之前,需要確認(rèn)Java方法調(diào)用關(guān)系已成功寫入數(shù)據(jù)庫中。

4.3.5.1. 生成所有的調(diào)用鏈

執(zhí)行當(dāng)前步驟時(shí),需要執(zhí)行main()方法的類名如下:

  1. test.jacg.TestRunnerGenAllGraph4Caller 

需要選擇classpath對(duì)應(yīng)模塊為test。

當(dāng)前步驟執(zhí)行的操作及使用的相關(guān)參數(shù)如下圖所示:

c.2.1 從數(shù)據(jù)庫讀取Java方法調(diào)用關(guān)系

TestRunnerGenAllGraph4Caller類讀取配置文件 o_g4caller_entry_method.properties ,該文件中指定了需要生成向下完整調(diào)用鏈的類名與方法名前綴,格式為[類名]:[方法名],或[類名]:[方法名]+參數(shù);

若存在同名類,則類名需要指定完整類名;若不存在同名類,則類名需要指定簡單類名;

示例如下:

  1. Test1:func1 
  2. Test1:func1( 
  3. Test1:func1(java.lang.String) 
  4. com.test.Test1:func1 
  5. com.test.Test1:func1( 
  6. com.test.Test1:func1(java.lang.String) 

若 o_g4caller_entry_method.properties 配置文件中指定的方法前綴對(duì)應(yīng)多個(gè)方法,則可在 o_g4caller_entry_method_ignore_prefix.properties 配置文件中指定需要忽略的方法前綴;

o_g4caller_entry_method_ignore_prefix.properties 配置文件的格式為方法名,或方法名+參數(shù),示例如下:

  1. func1 
  2. func1( 
  3. func1(java.lang.String) 

例如指定生成Class1.test方法的向下完整調(diào)用鏈,存在方法Class1.test1,則可指定忽略test1方法;指定生成Class1.test方法的向下完整調(diào)用鏈,所關(guān)注的test方法為test(java.lang.String),存在不關(guān)注的方法test(java.lang.Integer),則可指定忽略test(java.lang.Integer)方法;

讀取配置文件 config.properties 中的參數(shù):

thread.num :從數(shù)據(jù)庫并發(fā)讀取數(shù)據(jù)的線程數(shù)量,也是數(shù)據(jù)源連接池?cái)?shù)量;若 o_g4caller_entry_method.properties 配置文件中的記錄數(shù)比該值小,則會(huì)使用記錄數(shù)覆蓋該參數(shù)值

以下參數(shù)說明略:app.name、db.driver.name、db.url、db.username、db.password

c.2.2 將方法完整調(diào)用鏈(向下)寫入文件

對(duì)于配置文件 o_g4caller_entry_method.properties 中指定的方法,對(duì)每個(gè)方法生成一個(gè)對(duì)應(yīng)的文件,文件名為“[類名]@[方法名]@[完整方法名HASH+長度].txt”;

以上文件名示例為“TestClass1@func1@qDb0chxHzmPj1F26S7kzhw#048.txt”;

每次執(zhí)行時(shí)會(huì)生成一個(gè)新的目錄,用于保存輸出文件,目錄名格式為“~jacg_output_for_caller/[yyyyMMdd-HHmmss.SSS]”;

讀取配置文件 config.properties 中的參數(shù):

gen.combined.output :是否生成調(diào)用鏈的合并文件開關(guān),值為true/false;當(dāng)開關(guān)為開時(shí),在為各個(gè)類生成了對(duì)應(yīng)的調(diào)用鏈文件后,會(huì)生成一個(gè)將全部文件合并的文件,文件名為“~all-4caller.txt”

以下參數(shù)說明略:call.graph.output.detail、show.method.annotation。

4.3.5.2. 忽略特定的調(diào)用關(guān)系

以上生成指定方法向下的完整調(diào)用鏈中,包含了所有的方法調(diào)用鏈,可用于查找指定方法直接調(diào)用及間接調(diào)用的方法,例如通過調(diào)用的Mybatis的Mapper接口確認(rèn)該方法相關(guān)的數(shù)據(jù)庫表操作;

當(dāng)生成指定方法向下的完整調(diào)用鏈?zhǔn)菫榱巳斯し治龃a結(jié)構(gòu)時(shí),若包含了所有的方法調(diào)用鏈,則會(huì)有很多不重要的代碼產(chǎn)生干擾,例如對(duì)dto、entity等對(duì)象的讀取及賦值操作、通信數(shù)據(jù)序列化/反序列化操作(JSON等格式)、日期操作、流水號(hào)生成、請(qǐng)求字段格式檢查、注解/枚舉/常量/異常/日期相關(guān)類操作、Java對(duì)象默認(rèn)方法調(diào)用等;

調(diào)用以下類,支持將不關(guān)注的方法調(diào)用關(guān)系忽略:

  1. test.jacg.TestRunnerGenAllGraph4CallerSupportIgnore 

在配置文件 o_g4caller_ignore_class_keyword.properties 中可以指定需要忽略的類名關(guān)鍵字,可為包名中的關(guān)鍵字,或類名中的關(guān)鍵字,示例如下:

  1. .dto. 
  2. .entity. 
  3. Enum 
  4. Constant 

在配置文件 o_g4caller_ignore_full_method_prefix.properties 中可以指定需要忽略的完整方法前綴,可指定包名,或包名+類名,或包名+類名+方法名,或包名+類名+方法名+參數(shù),示例如下:

  1. com.test 
  2. com.test.Test1 
  3. com.test.Test1:func1 
  4. com.test.Test1:func1( 
  5. com.test.Test1:func1(java.lang.String) 

在配置文件 o_g4caller_ignore_method_prefix.properties 中可以指定需要忽略的方法名前綴,如Java對(duì)象中的默認(rèn)方法“toString()、hashCode()、equals(java.lang.Object)、<init>(、<clinit>(”等,示例如下:

  1. func1 
  2. func1(  
  3. func1() 
  4. func1(java.lang.String) 

5. 原理說明

5.1. Java方法調(diào)用關(guān)系獲取

在獲取Java方法調(diào)用關(guān)系時(shí),使用了 https://github.com/gousiosg/java-callgraph 項(xiàng)目,并對(duì)其進(jìn)行了增強(qiáng),java-callgraph使用Apache Commons BCEL(Byte Code Engineering Library)解析Java方法調(diào)用關(guān)系,Matthieu Vergne( https://www.matthieu-vergne.fr/ )為該項(xiàng)目增加了解析動(dòng)態(tài)調(diào)用的能力(lambda表達(dá)式等)。

原始java-callgraph在多數(shù)場(chǎng)景下能夠獲取到Java方法調(diào)用關(guān)系,但以下場(chǎng)景的調(diào)用關(guān)系會(huì)缺失:

接口與實(shí)現(xiàn)類方法

假如存在接口Interface1,及其實(shí)現(xiàn)類Impl1,若在某個(gè)類Class1中引入了接口Interface1,實(shí)際為實(shí)現(xiàn)類Impl1的實(shí)例(使用Spring時(shí)的常見場(chǎng)景),在其方法Class1.func1()中調(diào)用了Interface1.fi()方法;

原始java-callgraph生成的方法調(diào)用關(guān)系中,只包含Class1.func1()調(diào)用Interface1.fi()的關(guān)系,Class1.func1()調(diào)用Impl1.fi(),及Impl1.fi()向下調(diào)用的關(guān)系會(huì)缺失。

Runnable實(shí)現(xiàn)類線程調(diào)用

假如f1()方法中使用內(nèi)部匿名類形式的Runnable實(shí)現(xiàn)類在線程中執(zhí)行操作,在線程中執(zhí)行了f2()方法,如下所示:

  1. private void f1() { 
  2.     new Thread(new Runnable() { 
  3.         @Override 
  4.         public void run() { 
  5.             f2(); 
  6.         } 
  7.     }).start(); 

原始java-callgraph生成的方法調(diào)用關(guān)系中,f1()調(diào)用f2(),及f2()向下調(diào)用的關(guān)系會(huì)缺失;

對(duì)于使用命名類形式的Runnable實(shí)現(xiàn)類在線程中執(zhí)行操作的情況,存在相同的問題,原方法調(diào)用線程中執(zhí)行的方法,及繼續(xù)向下的調(diào)用關(guān)系會(huì)缺失。

Thread子類線程調(diào)用

與Runnable實(shí)現(xiàn)類線程調(diào)用情況類似,略。

lambda表達(dá)式(含線程調(diào)用等)

假如f1()方法中使用lambda表達(dá)式的形式在線程中執(zhí)行操作,在線程中執(zhí)行了f2()方法,如下所示:

  1. private void f1() { 
  2.     new Thread(() -> f2()).start(); 

原始java-callgraph生成的方法調(diào)用關(guān)系中,f1()調(diào)用f2(),及f2()向下調(diào)用的關(guān)系會(huì)缺失;

對(duì)于其他使用lambda表達(dá)式的情況,存在相同的問題,原方法調(diào)用lambda表達(dá)式中執(zhí)行的方法,及繼續(xù)向下的調(diào)用關(guān)系會(huì)缺失。

父類調(diào)用子類的實(shí)現(xiàn)方法

假如存在抽象父類Abstract1,及其非抽象子類ChildImpl1,若在某個(gè)類Class1中引入了抽象父類Abstract1,實(shí)際為子類ChildImpl1的實(shí)例(使用Spring時(shí)的常見場(chǎng)景),在其方法Class1.func1()中調(diào)用了Abstract1.fa()方法;

原始java-callgraph生成的方法調(diào)用關(guān)系中,只包含Class1.func1()調(diào)用Abstract1.fa()的關(guān)系,Class1.func1()調(diào)用ChildImpl1.fa()的關(guān)系會(huì)缺失。

子類調(diào)用父類的實(shí)現(xiàn)方法

假如存在抽象父類Abstract1,及其非抽象子類ChildImpl1,若在ChildImpl1.fc1()方法中調(diào)用了父類Abstract1實(shí)現(xiàn)的方法fi();

原始java-callgraph生成的方法調(diào)用關(guān)系中,ChildImpl1.fc1()調(diào)用Abstract1.fi()的關(guān)系會(huì)缺失。

針對(duì)以上問題,增強(qiáng)后的java-callgraph都進(jìn)行了優(yōu)化,能夠生成缺失的調(diào)用關(guān)系。

增強(qiáng)后的java-callgraph地址為 https://github.com/Adrninistrator/java-callgraph 。

對(duì)于更復(fù)雜的情況,例如存在接口Interface1,及其抽象實(shí)現(xiàn)類Abstract1,及其子類ChildImpl1,若在某個(gè)類中引入了抽象實(shí)現(xiàn)類Abstract1并調(diào)用其方法的情況,生成的方法調(diào)用關(guān)系中也不會(huì)出現(xiàn)缺失。

5.2. Java方法完整調(diào)用鏈生成

在獲取了Java方法調(diào)用關(guān)系之后,將其保存在數(shù)據(jù)庫中,涉及到3個(gè)數(shù)據(jù)庫表,可查看java-all-callgraph.jar釋放的~jacg_sql目錄中的.sql文件,相關(guān)數(shù)據(jù)庫表如下所示:

表名前綴 注釋 作用
class_name_ 類名信息表 保存相關(guān)類的完整類名及簡單類名
method_annotation_ 方法注解表 保存方法及方法上的注解信息
method_call_ 方法調(diào)用關(guān)系表 保存各方法之間調(diào)用信息

上述數(shù)據(jù)庫表在創(chuàng)建時(shí)使用表名前綴加上配置文件 config.properties 中的 app.name 參數(shù)值。

該工具會(huì)主要從方法調(diào)用關(guān)系表中逐級(jí)查詢數(shù)據(jù),生成完整的方法調(diào)用鏈。

6. 其他功能

6.1. 處理循環(huán)方法調(diào)用

在生成向上或向下的Java方法完整調(diào)用鏈時(shí),若出現(xiàn)了循環(huán)方法調(diào)用,該工具會(huì)從循環(huán)調(diào)用中跳出,并在生成的方法調(diào)用鏈中對(duì)出現(xiàn)循環(huán)調(diào)用的方法增加標(biāo)記“!cycle[n]!”,其中n代表被循環(huán)調(diào)用的方法對(duì)應(yīng)層級(jí)。

生成向上的Java方法完整調(diào)用鏈時(shí),出現(xiàn)循環(huán)方法調(diào)用的示例如下:

  1. [0]#org.springframework.transaction.TransactionDefinition:getIsolationLevel 
  2. [1]#  org.springframework.transaction.support.DelegatingTransactionDefinition:getIsolationLevel 
  3. [2]#    org.springframework.transaction.TransactionDefinition:getIsolationLevel !cycle[0]! 

生成向下的Java方法完整調(diào)用鏈時(shí),出現(xiàn)循環(huán)方法調(diào)用的示例如下:

  1. [0]#org.springframework.transaction.support.TransactionTemplate:execute 
  2. [1]#  org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager:execute 
  3. [2]#    org.springframework.transaction.jta.WebSphereUowTransactionManager:execute 
  4. [3]#      org.springframework.transaction.TransactionDefinition:getTimeout 
  5. [4]#        org.springframework.transaction.support.DefaultTransactionDefinition:getTimeout 
  6. [4]#        org.springframework.transaction.support.DelegatingTransactionDefinition:getTimeout 
  7. [5]#          org.springframework.transaction.TransactionDefinition:getTimeout  !cycle[3]! 

6.2. 生成兩個(gè)方法之間的調(diào)用鏈

該工具生成的向上或向下的Java方法完整調(diào)用鏈通常會(huì)比較大,如果只關(guān)注某個(gè)方法到起始方法之間的調(diào)用鏈時(shí),可以按照以下步驟生成:

執(zhí)行以下java類:

  1. com.adrninistrator.jacg.other.GenSingleCallGraph 

需要選擇classpath對(duì)應(yīng)模塊為test。

在程序參數(shù)(即main()方法處理的參數(shù))中指定對(duì)應(yīng)的向上或向下的Java方法完整調(diào)用鏈文件路徑,及關(guān)注的方法所在行數(shù),格式為“[完整調(diào)用鏈文件路徑] [關(guān)注方法所在行數(shù)]”。

當(dāng)文件路徑包含空格時(shí),需要使用""包含;關(guān)注方法所在行數(shù)從1開始。

例如完整調(diào)用鏈文件“dir/a.txt”內(nèi)容如下:

  1. [0]#DestClass.destfunc() 
  2. [1]#  ClassA3.funcA3() 
  3. [2]#    ClassA2.funcA2() 
  4. [3]#      ClassA1.funcA1()  !entry! 
  5. [1]#  ClassB1.funcB1()  !entry! 
  6. [1]#  ClassC2.funcC2() 
  7. [2]#    ClassC1.funcC1()    !entry! 

假如希望知道第7行“[2]# ClassC1.funcC1() !entry!”方法與起始方法“[0]#DestClass.destfunc()”之間的調(diào)用關(guān)系,可在執(zhí)行以上類時(shí)指定程序參數(shù)為“dir/a.txt 7”,則生成調(diào)用關(guān)系如下:

  1. [0]#DestClass.destfunc() 
  2. [1]#  ClassC2.funcC2() 
  3. [2]#    ClassC1.funcC1()    !entry! 

7. 分析腳本

在 https://github.com/Adrninistrator/java-all-call-graph 的“shell腳本”、“SQL語句”目錄中,保存了以下腳本,可以用于對(duì)代碼進(jìn)行一些分析操作。

7.1. shell腳本

根據(jù)Mybatis的Mapper查找對(duì)應(yīng)數(shù)據(jù)庫表名

根據(jù)數(shù)據(jù)庫表名查找Mybatis的對(duì)應(yīng)Mapper

根據(jù)向上完整調(diào)用鏈查找入口方法完整類名

根據(jù)向上完整調(diào)用鏈查找入口方法簡單類名

根據(jù)向下完整調(diào)用鏈查找被使用的Mapper完整類名

根據(jù)向下完整調(diào)用鏈查找被使用的Mapper方法

根據(jù)向下完整調(diào)用鏈查找被使用的Mapper簡單類名

7.2. SQL語句

針對(duì)該工具使用的數(shù)據(jù)庫表進(jìn)行分析的SQL語句。

8. 無法正確處理的情況

以下情況,對(duì)應(yīng)的方法找不到被調(diào)用關(guān)系,可能會(huì)被誤識(shí)別為入口方法:

不是直接通過Java方法進(jìn)行調(diào)用的情況(例如在XML文件中配置代碼執(zhí)行流程、通過注解配置代碼執(zhí)行流程、使用AOP處理等);

未被調(diào)用的方法;

方法作為流式處理的參數(shù),如“xxx.stream().filter(this::func)”。

9. 使用建議

可能存在以下問題:

當(dāng)一個(gè)接口對(duì)應(yīng)多個(gè)實(shí)現(xiàn)類時(shí),若在某個(gè)類中引入了接口,并調(diào)用其方法,生成的完整調(diào)用鏈中,可能將當(dāng)前類未使用的其他實(shí)現(xiàn)類相關(guān)的調(diào)用關(guān)系也包含進(jìn)來;

當(dāng)一個(gè)抽象父類對(duì)應(yīng)多個(gè)非抽象子類時(shí),若在某個(gè)類中引入了抽象父類,并調(diào)用其方法,生成的完整調(diào)用鏈中,可能將當(dāng)前類未使用的其他非抽象子類相關(guān)的調(diào)用關(guān)系也包含進(jìn)來。

對(duì)于以上問題,可以臨時(shí)修改代碼但不提交,將引入的接口使用實(shí)現(xiàn)類替代,或抽象父類使用非抽象子類替代,生成jar包/war包后生成調(diào)用關(guān)系,再重新生成完整調(diào)用鏈。

 

責(zé)任編輯:張燕妮 來源: FreeBuf
相關(guān)推薦

2021-05-25 09:59:50

前端開發(fā)工具

2021-04-23 23:27:00

數(shù)據(jù)資產(chǎn)IT運(yùn)營

2012-02-07 13:21:37

Java

2022-08-15 08:02:09

Go程序函數(shù)

2023-11-29 09:00:00

KubernetesDevOps

2014-03-18 14:30:51

游戲引擎cocos2d-x

2023-08-11 08:02:36

Gogonew工具鏈

2020-08-21 07:00:00

DevOpsIT開發(fā)

2021-03-06 08:05:54

工具DevOpsLighthouse

2021-03-14 22:34:05

工具RMS應(yīng)用層

2021-01-29 15:50:45

DevOps運(yùn)維

2011-08-29 09:54:45

LUAJAVA 方法

2012-03-01 13:34:02

Java

2009-12-08 17:15:43

PHP調(diào)用Java語言

2012-05-29 15:17:08

JavaWMIC

2009-06-17 13:19:50

Java調(diào)用DLL

2022-02-15 17:56:19

SpringBoot日志

2017-03-10 14:54:42

京東智慧供應(yīng)鏈

2017-09-07 19:21:20

Java語言Iodine
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)