日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用 Arthas 排查开源 Excel 组件问题

發布時間:2024/9/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 Arthas 排查开源 Excel 组件问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
簡介:有了實際的使用之后,不免會想到,Arthas 是如何做到在程序運行時,動態監測我們的代碼的呢?帶著這樣的問題,我們一起來看下 Java Agent 技術實現原理。

?

背景介紹

?

項目中有使用到 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() 方法,沒有將異常信息打印出來。而且本地復現也沒有復現出來。所以只能考慮使用 arthas 來協助排查這個問題了。
?

排查過程

?

1、線上服務器安裝 Arthas。
https://arthas.aliyun.com/doc/install-detail.html

2、使用 watch 命令監控指定方法,打印出異常的堆棧信息,命令如下:

watch com.github.dreamroute.excel.helper.ExcelHelper importFromFile '{params,throwExp}' -e -x 3

再次調用方法,捕獲到異常棧信息如下:


已經捕獲到異常,并打印出堆棧信息。
?

3、根據對應的堆棧信息,定位到具體的代碼,如下:


代碼很簡單,從代碼中可以很清晰的看到如果沒有從 headerInfoMap 中沒有獲取到指定的 headerInfo ,就會拋這個異常。沒有找到只有兩種情況:
?

  • headerInfoMap 中保存的信息不對。
  • cell 中的 columnIndex 超出的正常的范圍導致沒有獲取到對應 HeaderInfo 。

對于第二種情況,首先去校驗了一下上傳的 Excel 文件是否有問題,本地測試了一下 Excel 文件,沒有任何問題。本地測試也是成功的,所以主觀判斷,第二種情況的可能性不大。

所以說主要檢查第一種情況是否發生,這個時候可以再去看一下該方法的第一行代碼
?

MapheaderInfoMap = processHeaderInfo(rows,cls);

可以看到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 變量來做緩存,被調用時先去HEADER_INFO 中查,如果有則直接返回,沒有則重新創建(也就說明相同的 Excel 文件,僅初始化一次 HeaderInfo )。創建的步驟在 ClassAssistant.getHeaderInfo() 方法中。

簡單的看一下 HeaderInfo 的生成過程,根據 Excel 文件的第一行中的各個 Cell 值與自定義實體類的注解比較,如果名字相同,就存為一個鍵值對( HeaderInfo 的數據結構為 HashMap )。

4、這個時候需要再確認一下 HEADER_INFO 中保存的 ExcelDTO.class 相關的 HeaderInfo 是怎樣的。通過 ognl 命令或者 getstatic 命令來查看。這里使用 ognl 命令。
?

ognl '#value=new com.tom.dto.ExcelDTO(),#valueMap=@com.github.dreamroute.excel.helper.cache.CacheFactory@HEADER_INFO,#valueMap.get(#value.getClass()).entrySet().iterator.{#this.value.name}'

結果如下:正常情況下這個 Excel 文件有 6 列信息,為什么只產生了 4 個鍵值對呢?如果 HEADER_INFO 中保存了錯的,從上面的邏輯來看,后面上傳的正確的 Excel 文件在解析時都會拋錯。


5、詢問了當時發現這個問題的同事,得知他第一次上傳的 Excel 文件是有問題的,后面想改正,再上傳時便出現了問題。到這里問題也算是找到了。
?

Arthas 原理探究

?

有了實際的使用之后,不免會想到,Arthas 是如何做到在程序運行時,動態監測我們的代碼的呢?帶著這樣的問題,我們一起來看下 Java Agent 技術實現原理。

?

Java Agent 技術

?

Agent 是一個運行在目標 JVM 的特定程序,它的職責是負責從目標 JVM 中獲取數據,然后將數據傳遞給外部進程。加載 Agent 的時機可以是目標 JVM 啟動之時,也可以是在目標 JVM 運行時進行加載,而在目標 JVM 運行時進行 Agent 加載具備動態性。
?

基礎概念

?

  • JVMTI(JVM Tool Interface):是 JVM 暴露出來的一些供用戶擴展的接口集合,JVMTI 是基于事件驅動的,JVM 每執行到一定的邏輯就會調用一些事件的回調接口(如果有的話),這些接口可以供開發者去擴展自己的邏輯。
  • JVMTIAgent(JVM Tool Interface):是一個動態庫,利用 JVMTI 暴露出來的一些接口幫助我們在程序啟動時或程序運行時 JVM Attach 機制,將 Agent 加載到目標 JVM 中。
  • JPLISAgent(Java Programming Language Instrumentation Services Agent):它的作用是初始化所有通過 Java Instrumentation API 編寫的 Agent,并且也承擔著通過 JVMTI 實現 Java Instrumentation 中暴露 API 的責任。
  • VirtualMachine :提供了Attach 動作和 Detach 動作,允許我們通過 attach 方法,遠程連接到 JVM 上,然后通過 loadAgent 方法向 JVM 注冊一個代理程序 agent ,在該 agent 的代理程序中會得到一個 Instrumentation 實例,該實例可以在 class 加載前改變 class 的字節碼,也可以在 class 加載后重新加載。
  • Instrumentation:可以在 class 加載前改變 class 的字節碼(premain),也可以在 class 加載后重新加載(agentmain)。

?

執行過程

?

動手寫一個 Demo

?

通過 javassist,在運行時更改指定方法的代碼,在方法之前后添加自定義邏輯。
?

1、定義 Agent 類。當前 Java 提供了兩種方式可以將代碼代碼注入到 JVM 中,這里我們的 Demo 選擇使用 agentmain 方法來實現。

premain:在啟動時通過 javaagent 命令,將代理注入到指定的 JVM 中。
agentmain:運行時通過 attach 工具激活指定代理。
?

/*** AgentMain** @author tomxin*/ public class AgentMain {public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException, ClassNotFoundException {instrumentation.addTransformer(new InterceptorTransformer(agentArgs), true);Class clazz = Class.forName(agentArgs.split(",")[1]);instrumentation.retransformClasses(clazz);} }/*** InterceptorTransformer** @author tomxin*/ public class InterceptorTransformer implements ClassFileTransformer {private String agentArgs;public InterceptorTransformer(String agentArgs) {this.agentArgs = agentArgs;}@Overridepublic byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {//javassist的包名是用點分割的,需要轉換下if (className != null && className.indexOf("/") != -1) {className = className.replaceAll("/", ".");}try {//通過包名獲取類文件CtClass cc = ClassPool.getDefault().get(className);//獲得指定方法名的方法CtMethod m = cc.getDeclaredMethod(agentArgs.split(",")[2]);//在方法執行前插入代碼m.insertBefore("{ System.out.println(\"=========開始執行=========\"); }");m.insertAfter("{ System.out.println(\"=========結束執行=========\"); }");return cc.toBytecode();} catch (Exception e) {}return null;} }

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}) 來指定要代理的類。
?

import com.sun.tools.attach.VirtualMachine;import java.io.IOException;/*** AttachMain** @author tomxin*/ public class AttachMain {public static void main(String[] args) {VirtualMachine virtualMachine = null;try {virtualMachine = VirtualMachine.attach(args[0]);// 將打包好的Jar包,添加到指定的JVM進程中。virtualMachine.loadAgent("target/agent-demo-1.0-SNAPSHOT.jar",String.join(",", args));} catch (Exception e) {if (virtualMachine != null) {try {virtualMachine.detach();} catch (IOException ex) {ex.printStackTrace();}}}} }

?

4、定義測試的方法
?

package com.tom.mdc; import java.lang.management.ManagementFactory; import java.util.Random; import java.util.concurrent.TimeUnit;/*** PrintParamTarget** @author toxmxin*/ public class PrintParamTarget {public static void main(String[] args) {// 打印當前進程IDSystem.out.println(ManagementFactory.getRuntimeMXBean().getName());Random random = new Random();while (true) {int sleepTime = 5 + random.nextInt(5);running(sleepTime);}}private static void running(int sleepTime) {try {TimeUnit.SECONDS.sleep(sleepTime);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("running sleep time " + sleepTime);} }

原文鏈接:https://developer.aliyun.com/article/784923?

版權聲明:本文內容由阿里云實名注冊用戶自發貢獻,版權歸原作者所有,阿里云開發者社區不擁有其著作權,亦不承擔相應法律責任。具體規則請查看《阿里云開發者社區用戶服務協議》和《阿里云開發者社區知識產權保護指引》。如果您發現本社區中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社區將立刻刪除涉嫌侵權內容。

總結

以上是生活随笔為你收集整理的使用 Arthas 排查开源 Excel 组件问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。