記一次springboot專案結合arthas排查ClassNotFoundException問題
前言
前陣子業務部門的專案出現了一個很奇怪的問題,有個class明明存在,本地idea執行也沒問題,然後一發佈線上就出現ClassNotFoundException問題,而且線上這個class確實是存在的。本文就通過一個demo示例來複現這麼一個情況
demo示例
注:本文的專案框架為springboot2。本文僅演示ClassNotFoundException相關內容,並不模擬業務流
業務服務A
package com.example.helloloader.service; import org.springframework.stereotype.Service; @Service public class HelloService { public String hello(){ return "hello loader"; } }
元件B
@Component public class HelloServiceLoaderUtils implements ApplicationContextAware { private ApplicationContext applicationContext; public String invoke(String className){ try { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class clz = classLoader.loadClass(className); Object bean = applicationContext.getBean(clz); Method method = clz.getMethod("hello"); return (String) method.invoke(bean); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
服務A呼叫元件B
@SpringBootApplication(scanBasePackages = "com.example") public class HelloLoaderApplication implements ApplicationRunner { @Autowired private HelloServiceLoaderUtils helloServiceLoaderUtils; public static void main(String[] args) { SpringApplication.run(HelloLoaderApplication.class, args); } @Override public void run(ApplicationArguments args) throws Exception { System.out.println(helloServiceLoaderUtils.invoke(HelloService.class.getName())); } }
異常復現
如果通過本地idea進行呼叫,控制檯會正常打印出
hello loader
將業務服務A打包,通過
java -jar hello-loader-0.0.1-SNAPSHOT.jar
啟動訪問
出現了ClassNotFoundException異常
異常排查
class存在,卻找不到class,要麼就是類載入器錯了,要麼是class的位置錯了。因此通過arthas進行排查。對arthas不瞭解的朋友,可以檢視如下文章
java應用線上診斷神器--Arthas我們通過如下命令檢視com.example.helloloader.service.HelloService載入器
sc -d com.example.helloloader.service.HelloService
從圖片可以看出打包後的HelloService的類載入器為spring封裝過的載入器,因此用appClassLoader是載入不到HelloService
解決方法
1、方法一將appClassLoader改成spring封裝的載入器
做法就是將ClassLoader.getSystemClassLoader()改成
Thread.currentThread().getContextClassLoader()即可
改好重新打包。此時重新執行,觀察控制檯
當前類載入器:org.springframework.boot.loader.Laun[email protected] hello loader
2、方法二修改打包方式
將打包外掛由
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
切換成
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.example.helloloader.HelloLoaderApplication</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/lib </outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build>
切換打包方式後,重新執行
當前類載入器:[email protected] hello loader
此時正常輸出,且載入器為AppClassLoader。我們可以通過
sc -d com.example.helloloader.service.HelloService
觀察HelloService的類載入器
此時的HelloService的類載入器為AppClassLoader
總結
1、如果專案採用springboot的打包外掛,他的class會放在/BOOT-INF,且該目錄下的class類載入器為
org.springframework.boot.loader.LaunchedURLClassLoader
2、arthas是個好東西,誰用誰知道
3、當時業務排查的時候,過程是比我文章示例還要複雜一點。因為專案是部署到k8s中,當本地專案啟動沒問題時,業務方的研發就一直把問題聚焦在k8s中,一直覺得是k8s引發的問題。
後面他們業務方找到我,叫我幫忙排查,我第一反應就是可能打包出了問題,於是我讓業務方打個包,本地以java -jar試下,但業務方的研發又很肯定的說,他試過本地打jar執行也沒問題。因為業務方的程式碼我這邊是沒許可權訪問的,沒辦法進行驗證。後面只能建議他們安裝arthas,最終結合arthas解決了問題
- 社群精選 | 不容錯過的9個冷門css屬性
- 2022最新版 Redis大廠面試題總結(附答案)
- 手寫一個mini版本的React狀態管理工具
- 【vue3原始碼】十三、認識Block
- 天翼雲全場景業務無縫替換至國產原生作業系統CTyunOS!
- JavaScript 設計模式 —— 代理模式
- MobTech簡訊驗證ApiCloud端SDK
- 以羊了個羊為例,淺談小程式抓包與響應報文修改
- 這幾種常見的 JVM 調優場景,你知道嗎?
- 聊聊如何利用管道模式來進行業務編排(下篇)
- 通用ORM的設計與實現
- 如此狂妄,自稱高效能佇列的Disruptor有啥來頭?
- 為什麼要學習GoF設計模式?
- 827. 最大人工島 : 簡單「並查集 列舉」運用題
- 介紹 Preact Signals
- 手把手教你如何使用 Timestream 實現物聯網時序資料儲存和分析
- 850. 矩形面積 II : 掃描線模板題
- Java 併發程式設計解析 | 基於JDK原始碼解析Java領域中的併發鎖,我們可以從中學習到什麼內容?
- 令人困惑的 Go time.AddDate
- 壓測平臺在全鏈路大促壓測中的實踐