偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

拜托,別在 Agent 中依賴 Fastjson 了

開源
本來我查了下 maven-shade-plugin 似乎是可以在 agent 打包時(shí)把\META-INF\services\這個(gè)目錄排除掉的,這樣的話上面的問題也能解決掉,但是連續(xù)兩次踩了這個(gè)坑還是讓我靜下來好好思考了一下。

一、背景

最近因?yàn)樵黾恿艘粋€(gè)在 agent 中上報(bào)異常的功能,agent 為了在 http 請(qǐng)求時(shí)方便把對(duì)象轉(zhuǎn)換為 json 格式,增加了一個(gè) fastjson 的依賴,結(jié)果搞出來各種問題。

環(huán)境:

  • JDK 1.8
  • SpringBoot 2.0.0.RELEASE
  • skywalking agent 8.14.0

二、初現(xiàn)問題

2.1 初步定位

有同事反饋應(yīng)用在本地能啟動(dòng),但是到了測(cè)試環(huán)境(帶 agent 啟動(dòng))就起不來,報(bào)錯(cuò)如下:

圖片圖片

首先還是要確認(rèn)下是不是應(yīng)用的依賴沖突問題,GenericHttpMessageConverter這個(gè)類是在 spring-web 這個(gè)包下面的, 因?yàn)楸镜卮虬h(huán)境和測(cè)試環(huán)境有可能不一致,需要確認(rèn)最終部署到測(cè)試環(huán)境的包里是否包含了 spring-web 包。經(jīng)確認(rèn)包里有 spring-web,排除這個(gè)可能。

然后懷疑是 agent 和應(yīng)用的依賴沖突,臨時(shí)讓這個(gè)應(yīng)用的 agent 下線后重新部署,發(fā)現(xiàn)能正常啟動(dòng),基本確認(rèn)是 agent 帶來的問題。

2.2 進(jìn)一步排查

為了方便定位問題,我把發(fā)現(xiàn)問題時(shí)應(yīng)用部署的包下載到本地,并在本地掛載 agent 啟動(dòng),問題重現(xiàn),報(bào)錯(cuò)和測(cè)試環(huán)境一致。至此我就可以在本地 debug 了。

順便說一下,我一開始用 idea 啟動(dòng)應(yīng)用(掛載 agent)是沒問題的,至于為什么沒問題下面會(huì)說到。

本地我在java.net.URLClassLoader#findClass方法的入口處打了一個(gè)條件斷點(diǎn)(類名為GenericHttpMessageConverter的才會(huì)進(jìn)來),啟動(dòng)應(yīng)用后一會(huì)兒進(jìn)入斷點(diǎn)。

idea 這個(gè)工具就是好用,從 debug 界面一下子就能看出來,這個(gè) findClass 是調(diào)用了 3 次,并且能看到每一次 findClass 是加載的哪個(gè)類:

圖片圖片

從上面的圖的最后一行也能看出來,這個(gè)類加載最開始的觸發(fā)是在內(nèi)部的一個(gè)二方庫的類WebAutoConfig中觸發(fā)的。

這 3 次 findClass 的順序可以看出, 類的加載順序?yàn)?

BootMessageConverter (二方包)

-> FastJsonHttpMessageConverter (fastjson)

-> GenericHttpMessageConverter (spring-web)

再來看看WebAutoConfig觸發(fā)類加載的那段代碼:

@Configuration
public class WebAutoConfig implements WebMvcConfigurer {
  
  @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters httpMessageConverter() {
        BootMessageConverter converter = new BootMessageConverter(); //這一行觸發(fā)了類加載
    ...
    }
}

public class BootMessageConverter extends FastJsonHttpMessageConverter {
 ...
}

public class FastJsonHttpMessageConverter extends AbstractHttpMessageConverter<Object>
        implements GenericHttpMessageConverter<Object> {
 ...        
}

從上面的代碼能看出最開始是因?yàn)锽ootMessageConverter的實(shí)例化進(jìn)行了類加載, 而BootMessageConverter因?yàn)槔^承了FastJsonHttpMessageConverter, 又接著觸發(fā)了FastJsonHttpMessageConverter的類加載, 然后FastJsonHttpMessageConverter因?yàn)閷?shí)現(xiàn)了GenericHttpMessageConverter接口, 又進(jìn)一步觸發(fā)了GenericHttpMessageConverter的類加載, 這樣來看源碼和上面 debug 得出的結(jié)論是一致的。

分析到這一步,如果你對(duì)類加載機(jī)制以及 agent 的運(yùn)行方式非常熟悉的話,基本已經(jīng)能得出“為什么會(huì)報(bào)GenericHttpMessageConverter類找不到的錯(cuò)誤”結(jié)論了。

那么接下來,我會(huì)基于類加載的機(jī)制來詳細(xì)分析一下,為什么會(huì)找不到GenericHttpMessageConverter

三、類加載機(jī)制

3.1 雙親委派機(jī)制

圖片圖片

上一層類加載器是下一層類加載器的父加載器,除了 Bootstrap ClassLoader 之外,所有的加載器都是有父加載器的。

所謂的雙親委派機(jī)制,指的就是:當(dāng)一個(gè)類加載器收到了類加載的請(qǐng)求的時(shí)候,他不會(huì)直接去加載指定的類,而是把這個(gè)請(qǐng)求委托給自己的父加載器去加載。只有父加載器無法加載這個(gè)類的時(shí)候,才會(huì)由當(dāng)前這個(gè)加載器來負(fù)責(zé)類的加載。

開個(gè)玩笑:這樣說來,雙親委派這種說法似乎并不準(zhǔn)確,因?yàn)橛懈笩o母,準(zhǔn)確來說應(yīng)該是“單親委派”...

3.1.1 類中依賴的其他類是怎么加載的

----------------接下來是重點(diǎn)----------------

我們定義的類一般還會(huì)依賴其他類,因此在被類加載器加載時(shí),類加載機(jī)制中除了雙親委派機(jī)制之外,還有一個(gè)重要的機(jī)制是:

假設(shè)類 A 依賴類 B,那么哪個(gè) ClassLoader 找到了類 A,這個(gè) ClassLoader 也會(huì)嘗試去加載類 B(當(dāng)然類 B 的加載過程也遵循雙親委派)。

3.2 springboot 的類加載機(jī)制

springboot 項(xiàng)目打包之后的 jar 目錄結(jié)構(gòu)如下:

├─BOOT-INF
│  ├─classes
│  │  ├─應(yīng)用代碼
│  └─lib
│     ├─應(yīng)用依賴的jar包
├─META-INF
│  ├─MANIFEST.MF
└─org
    └─springframework
        └─boot
            └─loader
                │  JarLauncher.class
                │  LaunchedURLClassLoader.class
                │  Launcher.class
                │  ...

其中/META-INF/MANIFEST.MF 是 jar 包運(yùn)行的關(guān)鍵, 來看一下里面的內(nèi)容:

...

Main-Class: org.springframework.boot.loader.JarLauncher

Start-Class: com.xxxxxx.DemoApplication

Spring-Boot-Classes: BOOT-INF/classes/

Spring-Boot-Lib: BOOT-INF/lib/

...

首先 jar 包運(yùn)行都有一個(gè)入口類定義了 main 方法,可以看到 springboot 項(xiàng)目打包出來的 jar 定義的入口運(yùn)行類并不是應(yīng)用代碼中的XxxApplication,而是 springboot 中的一個(gè)類JarLauncher,那么應(yīng)用代碼中的XxxApplication是怎么運(yùn)行的呢?

當(dāng)你運(yùn)行 java -jar 命令的時(shí)候,JarLauncher會(huì)加載 /BOOT-INF/classes 下的類和 /BOOT-INF/lib 下的 jar 包。最后調(diào)用 MANIFEST.MF 文件的 Start-Class 屬性指定的類的 main 方法來完成應(yīng)用程序的啟動(dòng)。

問題是 /BOOT-INF/ 并不是標(biāo)準(zhǔn)的 classpath 路徑,系統(tǒng)內(nèi)置的 ClassLoader 是加載不到這些目錄的類的,那么這些類是誰來加載的呢?答案就是 springboot 自定義的類加載器:LaunchedURLClassLoader

圖片圖片

也就是說應(yīng)用代碼中的類以及應(yīng)用依賴的 jar 都是LaunchedURLClassLoader負(fù)責(zé)加載的。

3.3 fastjson 的類到底是怎么找到的

再說回來在第 2.2 節(jié)中說到的類加載順序:

BootMessageConverter (二方包)

-> FastJsonHttpMessageConverter (fastjson)

-> GenericHttpMessageConverter (spring-web)

這里我們重點(diǎn)來分析一下中間那個(gè)FastJsonHttpMessageConverter到底是怎么被加載的。

已知應(yīng)用依賴了 fastjson 和 spring-web,agent 也依賴了 fastjson 但不依賴 spring-web。

從 Oracle 官方的文檔看到,Java 8 的 agent 的 jar 包里的類會(huì)添加到 classpath 中,因此會(huì)用AppClassLoader來加載。

圖片圖片

而二方包的BootMessageConverter是應(yīng)用依賴的 jar, 放在/BOOT-INF/lib 下, 因此是被LaunchedURLClassLoader加載的。整體類加載流程如下圖:

圖片圖片

上圖說明:

當(dāng)BootMessageConverter被LaunchedURLClassLoader加載時(shí), 發(fā)現(xiàn)依賴了FastJsonHttpMessageConverter, 因此LaunchedURLClassLoader會(huì)繼續(xù)嘗試去加載FastJsonHttpMessageConverter。由于類加載的雙親委派機(jī)制,LaunchedURLClassLoader會(huì)委派它的父加載器AppClassLoader來嘗試加載,當(dāng)然AppClassLoader會(huì)繼續(xù)往上找父加載器,一直到Bootstrap ClassLoader。

很顯然,Bootstrap ClassLoader和ExtClassLoader都無法找到FastJsonHttpMessageConverter,但是AppClassLoader可以找到(因?yàn)?agent 包中存在 fastjson 的類)。然后,這一步是關(guān)鍵,AppClassLoader找到了FastJsonHttpMessageConverter之后發(fā)現(xiàn)它依賴了GenericHttpMessageConverter,因此由找到了FastJsonHttpMessageConverter的AppClassLoader繼續(xù)嘗試加載GenericHttpMessageConverter,但是GenericHttpMessageConverter只有應(yīng)用依賴的 spring-web.jar 中才有,而這個(gè) jar 在/BOOT-INF/lib 下,只能被LaunchedURLClassLoader加載。雙親委派機(jī)制只能由子加載器往父加載器委托而反過來是不行的,而GenericHttpMessageConverter沒辦法被AppClassLoader以及它的父加載器加載到,因此AppClassLoader拋出了找不到GenericHttpMessageConverter的錯(cuò)誤。

這里的關(guān)鍵就在于LaunchedURLClassLoader本身是能找到 fastjson 類的(在/BOOT-INF/lib), 但是因?yàn)殡p親委派機(jī)制, 在加載 fastjson 類的時(shí)候, 被AppClassLoader截胡了,以至于喪失了后面依賴的類加載主動(dòng)權(quán)。

說到這里,就可以回答之前的那個(gè)問題了:為什么用 idea 啟動(dòng)應(yīng)用(掛載 agent)是沒問題的?因?yàn)?idea 是直接運(yùn)行應(yīng)用的 XxxApplication 類的 main 方法,不是通過 springboot 的JarLauncher啟動(dòng)的,而在運(yùn)行時(shí)所有的依賴都是通過指定 classpath 來做的,因此 idea 運(yùn)行過程中,所有的類都能通過AppClassLoader加載到,也就不存在上面這種沖突問題了。

四、解決方案一:maven-shade-plugin

知道問題的根因了,那么思路就是怎么樣可以讓 fastjson 類被LaunchedURLClassLoader找到而不要被AppClassLoader找到。這里的思路是把 agent 中依賴的 fastjson 的 package 給重命名一下。

maven-shade-plugin在 maven 官方網(wǎng)站中提供的一個(gè)插件,官方文檔中定義其功能如下:

This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade - i.e. rename - the packages of some of the dependencies.

簡(jiǎn)單來說就是將依賴的包在 package 階段一起打入 jar 包中,以及對(duì)依賴的 jar 包進(jìn)行重命名從而達(dá)到隔離的作用。接下來就把這個(gè) maven 插件引入 agent 中。

maven 配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <shadedArtifactAttached>false</shadedArtifactAttached>
                <createDependencyReducedPom>true</createDependencyReducedPom>
                <createSourcesJar>true</createSourcesJar>
                <shadeSourcesContent>true</shadeSourcesContent>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Premain-Class>xxxxxx.AgentStarter</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </transformer>
                </transformers>
                <!-- 這段是package重命名的關(guān)鍵配置 -->
                <relocations>
                    <relocation>
                        <pattern>com.alibaba.fastjson</pattern>
                        <shadedPattern>shade.com.alibaba.fastjson</shadedPattern>
                    </relocation>
                </relocations>
            </configuration>
        </execution>
    </executions>
</plugin>

package 之后的效果:

圖片圖片

可以看到在 agent 包中,fastjson 類的 package 都已經(jīng)加上了一個(gè)前綴shade.,這樣的話,應(yīng)用中加載正常的 fastjson 類的時(shí)候,肯定不會(huì)找到 agent 里面來了,以此避免了類加載被AppClassLoader截胡的情況。

用重新 package 的 agent 包啟動(dòng)之前應(yīng)用, 應(yīng)用正常啟動(dòng), 至此問題解決。

五、再現(xiàn)問題

本以為問題已經(jīng)解決,沒想到幾天后另一個(gè)應(yīng)用又報(bào)了類找不到的錯(cuò)誤:

圖片圖片

有了上次的經(jīng)驗(yàn), 這次還算順利, 排查過程跟上次的差不多。

最后發(fā)現(xiàn)是應(yīng)用依賴的 jersey 這個(gè)三方庫,而 jersey 通過 SPI 的方式會(huì)去找所有 classpath 中\(zhòng)META-INF\services\目錄下的javax.ws.rs.ext.MessageBodyReader這個(gè)文件,由于 agent 依賴了 fastjson,而 fastjson 也實(shí)現(xiàn)了這個(gè) SPI 的擴(kuò)展,結(jié)果 jersey 就找到了 agent 包的\META-INF\services\目錄下的javax.ws.rs.ext.MessageBodyReader文件,而javax.ws.rs.ext.MessageBodyReader文件中的內(nèi)容如下:

圖片圖片

可以看到 maven-shade-plugin 把這里的類 package 也改掉了。然后 jersey 讀取到這個(gè)文件后,根據(jù)類名去加載了shade.com.alibaba.fastjson.support.jaxrs.FastJsonProvider這個(gè)類,結(jié)果肯定是找到了 agent 包里的這個(gè)類,而這個(gè)類依賴的MessageBodyReader類是在 jsr311-api.jar 里的, 這個(gè) jar 包只在應(yīng)用中依賴, agent 并不依賴這個(gè) jar 包, 因此就拋出了找不到類的錯(cuò)誤。

依賴沖突真是讓人防不勝防~

六、決定:干掉 fastjson

本來我查了下 maven-shade-plugin 似乎是可以在 agent 打包時(shí)把\META-INF\services\這個(gè)目錄排除掉的,這樣的話上面的問題也能解決掉,但是連續(xù)兩次踩了這個(gè)坑還是讓我靜下來好好思考了一下。

這兩次的依賴沖突從根本上來看,都是因?yàn)?fastjson 做的太重,第一次是因?yàn)?fastjson 依賴了 spring,第二次是因?yàn)?fastjson 實(shí)現(xiàn)了 jsr311-api,而在 agent 中去依賴 fastjson 并沒有那么多的需求,只是為了做一個(gè)純粹的轉(zhuǎn)換工作:Java 對(duì)象和 Json 串之間的互相轉(zhuǎn)換。所以找一個(gè)純粹的輕量級(jí)的 Json 轉(zhuǎn)換庫是我的本質(zhì)需求。否則 fastjson 下次可能又遇到其他的依賴沖突問題,我還得改。

如何考量是否輕量級(jí)呢?我主要從兩方面著手:

  1. 看這個(gè)三方庫的 pom.xml 中有沒有依賴其他三方庫
  2. 看這個(gè)三方庫的\META-INF\services\目錄有沒有多余的 SPI 實(shí)現(xiàn)

最終我選擇了 Google 的 gson 作為 agent 依賴的 Json 轉(zhuǎn)換庫。

責(zé)任編輯:武曉燕 來源: 捉蟲大師
相關(guān)推薦

2018-09-28 05:25:53

TopK算法代碼

2020-04-22 11:19:07

貪心算法動(dòng)態(tài)規(guī)劃

2018-11-01 13:49:23

桶排序排序面試

2018-10-28 22:37:00

計(jì)數(shù)排序排序面試

2021-01-22 10:09:23

簡(jiǎn)歷求職者面試

2020-03-30 17:20:54

B+樹SQL索引

2019-03-12 14:48:29

路由器XBOXPS4

2025-02-10 08:05:03

2020-09-22 09:05:45

MySQLUTF-8utf8mb4

2025-06-18 02:01:00

Agent產(chǎn)品模型

2024-07-02 11:05:03

依賴倒置系統(tǒng)

2021-01-20 07:28:02

nullcollections對(duì)象

2023-11-02 08:43:08

protocgo兼容

2020-09-21 07:00:00

語音識(shí)別AI人工智能

2023-03-26 09:17:42

CRUD項(xiàng)目線程池

2019-06-27 17:18:02

Java日志編程語言

2022-03-14 10:14:43

底層系統(tǒng)Nacos

2025-07-22 11:56:26

2024-04-08 10:12:20

GPT4AgentAI

2021-10-09 12:53:14

惡意軟件黑客網(wǎng)絡(luò)攻擊
點(diǎn)贊
收藏

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