使用 Arthas 排查开源 Excel 组件问题
?
背景介紹
?
項目中有使用到 com.github.dreamroute excel-helper 這個工具來輔助 Excel 文件的解析,出錯時的代碼是這樣寫的:如下所示(非源代碼)
try { excelDTOS = ExcelHelper.importFromFile(ExcelType.XLSX, file, ExcelDTO.class); } catch (Exception e) { log.error("ExcelHelper importFromFile exception msg {}", e.getMessage()); }?
因為打印異常信息時,使用了 e.getMessage() 方法,沒有將異常信息打印出來。而且本地復(fù)現(xiàn)也沒有復(fù)現(xiàn)出來。所以只能考慮使用 arthas 來協(xié)助排查這個問題了。
?
排查過程
?
1、線上服務(wù)器安裝 Arthas。
https://arthas.aliyun.com/doc/install-detail.html
2、使用 watch 命令監(jiān)控指定方法,打印出異常的堆棧信息,命令如下:
watch com.github.dreamroute.excel.helper.ExcelHelper importFromFile '{params,throwExp}' -e -x 3再次調(diào)用方法,捕獲到異常棧信息如下:
已經(jīng)捕獲到異常,并打印出堆棧信息。
?
3、根據(jù)對應(yīng)的堆棧信息,定位到具體的代碼,如下:
代碼很簡單,從代碼中可以很清晰的看到如果沒有從 headerInfoMap 中沒有獲取到指定的 headerInfo ,就會拋這個異常。沒有找到只有兩種情況:
?
- headerInfoMap 中保存的信息不對。
- cell 中的 columnIndex 超出的正常的范圍導致沒有獲取到對應(yīng) HeaderInfo 。
對于第二種情況,首先去校驗了一下上傳的 Excel 文件是否有問題,本地測試了一下 Excel 文件,沒有任何問題。本地測試也是成功的,所以主觀判斷,第二種情況的可能性不大。
所以說主要檢查第一種情況是否發(fā)生,這個時候可以再去看一下該方法的第一行代碼
?
可以看到headerInfoMap是通過processHeaderInfo中獲取的。找到processHeaderInfo 的代碼,如下所示。
public static MapproceeHeaderInfo(Iteratorrows, Class cls) {if (rows.hasNext()) {Row header = rows.next();return CacheFactory.findHeaderInfo(cls, header);}return new HashMap<>(0); } public static MapfindHeaderInfo(Class cls, Row header) {MapheaderInfo = HEADER_INFO.get(cls);if (MapUtils.isEmpty(headerInfo)) {headerInfo = ClassAssistant.getHeaderInfo(cls, header);HEADER_INFO.put(cls, headerInfo);}return headerInfo; } public static MapgetHeaderInfo(Class cls, Row header) {IteratorcellIterator = header.cellIterator();Listfields = ClassAssistant.getAllFields(cls);MapheaderInfo = new HashMap<>(fields.size());while (cellIterator.hasNext()) {org.apache.poi.ss.usermodel.Cell cell = cellIterator.next();String headerName = cell.getStringCellValue();for (Field field : fields) {Column col = field.getAnnotation(Column.class);String name = col.name();if (Objects.equals(headerName, name)) {HeaderInfo hi = new HeaderInfo(col.cellType(), field);headerInfo.put(cell.getColumnIndex(), hi);break;}}}return headerInfo; }?
主要通過 CacheFactory 類的 findHeaderInfo 來生成,在 findHeaderInfo 方法中,通過一個被 static final 修飾的 HEADER_INFO 變量來做緩存,被調(diào)用時先去HEADER_INFO 中查,如果有則直接返回,沒有則重新創(chuàng)建(也就說明相同的 Excel 文件,僅初始化一次 HeaderInfo )。創(chuàng)建的步驟在 ClassAssistant.getHeaderInfo() 方法中。
簡單的看一下 HeaderInfo 的生成過程,根據(jù) Excel 文件的第一行中的各個 Cell 值與自定義實體類的注解比較,如果名字相同,就存為一個鍵值對( HeaderInfo 的數(shù)據(jù)結(jié)構(gòu)為 HashMap )。
4、這個時候需要再確認一下 HEADER_INFO 中保存的 ExcelDTO.class 相關(guān)的 HeaderInfo 是怎樣的。通過 ognl 命令或者 getstatic 命令來查看。這里使用 ognl 命令。
?
結(jié)果如下:正常情況下這個 Excel 文件有 6 列信息,為什么只產(chǎn)生了 4 個鍵值對呢?如果 HEADER_INFO 中保存了錯的,從上面的邏輯來看,后面上傳的正確的 Excel 文件在解析時都會拋錯。
5、詢問了當時發(fā)現(xiàn)這個問題的同事,得知他第一次上傳的 Excel 文件是有問題的,后面想改正,再上傳時便出現(xiàn)了問題。到這里問題也算是找到了。
?
Arthas 原理探究
?
有了實際的使用之后,不免會想到,Arthas 是如何做到在程序運行時,動態(tài)監(jiān)測我們的代碼的呢?帶著這樣的問題,我們一起來看下 Java Agent 技術(shù)實現(xiàn)原理。
?
Java Agent 技術(shù)
?
Agent 是一個運行在目標 JVM 的特定程序,它的職責是負責從目標 JVM 中獲取數(shù)據(jù),然后將數(shù)據(jù)傳遞給外部進程。加載 Agent 的時機可以是目標 JVM 啟動之時,也可以是在目標 JVM 運行時進行加載,而在目標 JVM 運行時進行 Agent 加載具備動態(tài)性。
?
基礎(chǔ)概念
?
- JVMTI(JVM Tool Interface):是 JVM 暴露出來的一些供用戶擴展的接口集合,JVMTI 是基于事件驅(qū)動的,JVM 每執(zhí)行到一定的邏輯就會調(diào)用一些事件的回調(diào)接口(如果有的話),這些接口可以供開發(fā)者去擴展自己的邏輯。
- JVMTIAgent(JVM Tool Interface):是一個動態(tài)庫,利用 JVMTI 暴露出來的一些接口幫助我們在程序啟動時或程序運行時 JVM Attach 機制,將 Agent 加載到目標 JVM 中。
- JPLISAgent(Java Programming Language Instrumentation Services Agent):它的作用是初始化所有通過 Java Instrumentation API 編寫的 Agent,并且也承擔著通過 JVMTI 實現(xiàn) Java Instrumentation 中暴露 API 的責任。
- VirtualMachine :提供了Attach 動作和 Detach 動作,允許我們通過 attach 方法,遠程連接到 JVM 上,然后通過 loadAgent 方法向 JVM 注冊一個代理程序 agent ,在該 agent 的代理程序中會得到一個 Instrumentation 實例,該實例可以在 class 加載前改變 class 的字節(jié)碼,也可以在 class 加載后重新加載。
- Instrumentation:可以在 class 加載前改變 class 的字節(jié)碼(premain),也可以在 class 加載后重新加載(agentmain)。
?
執(zhí)行過程
?
動手寫一個 Demo
?
通過 javassist,在運行時更改指定方法的代碼,在方法之前后添加自定義邏輯。
?
1、定義 Agent 類。當前 Java 提供了兩種方式可以將代碼代碼注入到 JVM 中,這里我們的 Demo 選擇使用 agentmain 方法來實現(xiàn)。
premain:在啟動時通過 javaagent 命令,將代理注入到指定的 JVM 中。
agentmain:運行時通過 attach 工具激活指定代理。
?
2、使用 Maven 配置 MANIFEST.MF 文件,該文件能夠指定 Jar 包的 main 方法。
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.3.1</version><configuration><archive><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Agent-Class>com.tom.mdc.AgentMain</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin></plugins></build>3、定義 Attach 方法,通過 VirtualMachine.attach(#{pid}) 來指定要代理的類。
?
?
4、定義測試的方法
?
原文鏈接:https://developer.aliyun.com/article/784923?
版權(quán)聲明:本文內(nèi)容由阿里云實名注冊用戶自發(fā)貢獻,版權(quán)歸原作者所有,阿里云開發(fā)者社區(qū)不擁有其著作權(quán),亦不承擔相應(yīng)法律責任。具體規(guī)則請查看《阿里云開發(fā)者社區(qū)用戶服務(wù)協(xié)議》和《阿里云開發(fā)者社區(qū)知識產(chǎn)權(quán)保護指引》。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,填寫侵權(quán)投訴表單進行舉報,一經(jīng)查實,本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。總結(jié)
以上是生活随笔為你收集整理的使用 Arthas 排查开源 Excel 组件问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 总分第一!阿里云数据库应用迁移解决方案通
- 下一篇: 未来,让我们一起想象 — “Imagin