Byte-Buddy系列 - 第4講 byte-buddy無法讀取到SpringBoot Jar中的類

目錄

    • 一、問題描述
    • 二、原因分析
    • 三、解決方案1(推薦):獲取線程上下文中的類加載器
      • 擴展
    • 四、解決方案2:自定義SpringBoot類加載器

一、問題描述

在使用Byte-Buddy中的TypePool對類進行擴展后,在本地開發集成環境(Intellij Idea)中可以正常運行,其中被擴展的類com.xx.yourClass是某個maven 依賴中的類(在開發環境沒法直接進行編輯),具體擴展代碼示例如下:

//使用TypePool + Redefine擴展屬性
TypePool typePool = TypePool.Default.ofSystemLoader();
Class bar = new ByteBuddy().redefine(typePool.describe("com.xx.yourClass").resolve(), ClassFileLocator.ForClassLoader.ofSystemLoader()).defineField("qux", String.class) .make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
Field quxField = bar.getDeclaredField("qux");
Assert.isTrue(Objects.nonNull(quxField), "qux field is null");

但是通過SpringBoot打包(底層依賴spring-boot-maven-plugin進行打包)成jar后,運行jar報如下錯誤,即無法加載到被擴展的類(某個maven依賴中的類):

# 使用TypePool報錯
net.bytebuddy.pool.TypePool$Resolution$NoSuchTypeException: Cannot resolve type description for com.xxx.YourClass
# 擴展 - 使用Redefine可能會報如下錯誤
java.lang.IllegalStateException: Could not locate class file for com.xxx.YourClass

二、原因分析

可以發現上述示例代碼中有3處使用了系統類加載器:

TypePool typePool = TypePool.Default.ofSystemLoader()
refedine(.., ClassFileLocator.ForClassLoader.ofSystemLoader())
load(ClassLoader.getSystemClassLoader(), ...)

問題就出在這塊,SpingBoot打包后的Jar文件有其特殊的層次結構,無法通過系統的類加載器加載到內嵌jar(即/BOOT-INF/lib/*.jar)中的類,而是需要通過SpringBoot自身提供的類加載器org.springframework.boot.loader.launch.LaunchedClassLoader進行加載,所以如上代碼通過系統類加載器SystemClassLoader是無法加載到內嵌jar中的類的。

SpringBoot打包后的Jar文件結構示例如下:
在這里插入圖片描述

三、解決方案1(推薦):獲取線程上下文中的類加載器

具體的解決方法就是如何獲取并設置byte-buddy使用SpringBoot自身提供的類加載器org.springframework.boot.loader.launch.LaunchedClassLoader,可以通過個取巧的方式獲取SpringBoot的類加載器。

在應用中植入如下代碼,即分別獲取線程上下文中的類加載器、系統加載器、平臺加載器:

System.out.println("Thread.CurrentThread.ContextClassLoader=" + Thread.currentThread().getContextClassLoader().getClass().getName());
System.out.println("SystemClassLoader=" + ClassLoader.getSystemClassLoader().getClass().getName());
System.out.println("PlatformClassLoader=" + ClassLoader.getPlatformClassLoader().getClass().getName());

直接在本地開發集成環境(Intellij Idea)中執行,打印結果如下:

Thread.CurrentThread.ContextClassLoader=jdk.internal.loader.ClassLoaders$AppClassLoader
SystemClassLoader=jdk.internal.loader.ClassLoaders$AppClassLoader
PlatformClassLoader=jdk.internal.loader.ClassLoaders$PlatformClassLoader

通過SpringBoot打包成Jar,運行Jar后打印結果如下:

Thread.CurrentThread.ContextClassLoader=org.springframework.boot.loader.launch.LaunchedClassLoader
SystemClassLoader=jdk.internal.loader.ClassLoaders$AppClassLoader
PlatformClassLoader=jdk.internal.loader.ClassLoaders$PlatformClassLoader

可以發現,在運行SpringBoot Jar后SpringBoot會將線程上下文中的類加載器(即Thread.currentThread().getContextClassLoader())設置為SpringBoot自身的類加載器LaunchedClassLoader,如此即可通過獲取線程上下文中的類加載器的方式來兼容本地開發環境和SpringBoot Jar中的類都能被正確加載。

調整最開始的示例代碼,將所有使用系統類加載器的地方都調整為使用線程上下文中的類加載器,調整后代碼如下:

//調整1:獲取SpringBoot內置的類加載器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
ClassFileLocator springBootClassFileLocator = ClassFileLocator.ForClassLoader.of(contextClassLoader);
//調整2:使用線程上下文中的SpringBoot內置的類加載器
TypePool typePool = TypePool.Default.of(springBootClassFileLocator);
Class bar = new ByteBuddy().redefine(typePool.describe("com.xx.yourClass").resolve(), //調整3:使用線程上下文中的SpringBoot內置的類加載器springBootClassFileLocator).defineField("qux", String.class) .make()//調整4:使用線程上下文中的SpringBoot內置的類加載器.load(contextClassLoader, ClassLoadingStrategy.Default.INJECTION).getLoaded();
Field quxField = bar.getDeclaredField("qux");
Assert.isTrue(Objects.nonNull(quxField), "qux field is null");

搞定!


擴展

如果不使用TypePool,而是普通的redefinerebase等操作,此時是可以直接獲取到被擴展的類的(xx.class而不是類的字符串表示),注意這時只需將所有跟類加載器相關的統一調整為根據被擴展類進行獲取即可,如下示例統一根據被擴展類YourClass獲取類加載器:

ByteBuddyAgent.install();
new ByteBuddy().redefine(YourClass.class, ClassFileLocator.ForClassLoader.of(YourClass.class.getClassLoader()))//省略....make().load(YourClass.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

四、解決方案2:自定義SpringBoot類加載器

大力出奇跡,可以在byte-buddy中自定義SpringBoot的類加載器實現,
具體參見:https://github.com/raphw/byte-buddy/issues/1470#issuecomment-1617556513,
即自行遍歷"/BOOT-INF/**/*.jar"的所有jar來加載類,具體自定義SpringBootClassFileLocator 實現代碼如下:

import net.bytebuddy.dynamic.ClassFileLocator;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;/*** SpringBoot復合類加載器** @author luohq* @date 2025-04-24* @link <a href="https://github.com/raphw/byte-buddy/issues/1470#issuecomment-1617556513">https://github.com/raphw/byte-buddy/issues/1470#issuecomment-1617556513</a>*/
public class SpringBootClassFileLocator {private SpringBootClassFileLocator() {}/*** 獲取SpringBoot復合類加載器** @return SpringBoot復合類加載器*/public static ClassFileLocator ofCompound() {try {String basePath = SpringBootClassFileLocator.class.getResource("/").getPath();List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>();classFileLocators.add(ClassFileLocator.ForClassLoader.ofSystemLoader());if (basePath != null && basePath.contains("BOOT-INF")) {String matchPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/BOOT-INF/**/*.jar";ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();Resource[] resources = resourcePatternResolver.getResources(matchPattern);for (Resource resource : resources) {ClassFileLocator classFileLocator = transform(resource);if (classFileLocator != null) {classFileLocators.add(classFileLocator);}}} else {classFileLocators.add(ClassFileLocator.ForClassLoader.ofPlatformLoader());}return new ClassFileLocator.Compound(classFileLocators);} catch (IOException ioe) {throw new RuntimeException("Init SpringBoot ClassFileLocator Exception!", ioe);}}/*** 轉換資源為ClassFileLocator** @param resource 資源* @return ClassFileLocator* @throws IOException IO異常*/private static ClassFileLocator transform(Resource resource) throws IOException {try (InputStream inputStream = resource.getInputStream()) {if (inputStream != null) {if (resource.getFilename().endsWith("jar")) {File tempFile = File.createTempFile("temp/jar/" + resource.getFilename(), ".jar");Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);return ClassFileLocator.ForJarFile.of(tempFile);}}}return null;}
}

SpringBootClassFileLocator 集成示例如下:

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//調整:使用自定義的SpringBoot類加載器
ClassFileLocator springBootClassFileLocator = SpringBootClassFileLocator.ofCompound();
TypePool typePool = TypePool.Default.of(springBootClassFileLocator);
Class bar = new ByteBuddy().redefine(typePool.describe("com.xx.yourClass").resolve(), springBootClassFileLocator).defineField("qux", String.class) .make().load(contextClassLoader, ClassLoadingStrategy.Default.INJECTION).getLoaded();
Field quxField = bar.getDeclaredField("qux");
Assert.isTrue(Objects.nonNull(quxField), "qux field is null");

實際測試加載速度不如解決方案1,且還需額外維護SpringBootClassFileLocator 實現,綜合對比還是更推薦解決方案1解決方案2可以作為一個備選方案。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903424.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903424.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903424.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AutogenStudio使用

官網介紹&#xff1a;https://microsoft.github.io/autogen/stable/ Autogen是什么&#xff1f; AutoGen 是由微軟開發的一個開源框架&#xff0c;旨在通過 多智能體協作&#xff08;Multi-Agent Collaboration&#xff09; 實現復雜的任務自動化。它的核心思想是讓多個 AI 代…

Vue3 Echarts 3D圓形柱狀圖實現教程以及封裝一個可復用的組件

文章目錄 前言一、實現原理二、series ——type: "pictorialBar" 簡介2.1 常用屬性 三、代碼實戰3.1 封裝一個echarts通用組件 echarts.vue3.2 首先實現一個基礎柱狀圖3.3 添加上下2個橢圓面3.4 進階封裝一個可復用的3D圓形柱狀圖組件 總結 前言 在前端開發的數據可視…

yolov8中train、test、val

說明yolov8中train、test、val是什么意思&#xff0c;是什么作用呢&#xff1f;詳細介紹使用yolov8進行實例分割&#xff0c;我應該如何制作我的數據集呢&#xff1f; 1. YOLOv8中的train、val、test是什么意思&#xff1f;作用是什么&#xff1f; 在YOLOv8&#xff08;由Ultr…

借助Spring AI實現智能體代理模式:從理論到實踐

借助Spring AI實現智能體代理模式&#xff1a;從理論到實踐 前言 在人工智能領域&#xff0c;大語言模型&#xff08;LLM&#xff09;的應用愈發廣泛&#xff0c;如何高效構建基于LLM的系統成為眾多開發者關注的焦點。Anthropic的研究報告《構建高效代理》為我們提供了新的思…

【學習筆記】計算機操作系統(二)—— 進程的描述與控制

第二章 進程的描述與控制 文章目錄 第二章 進程的描述與控制2.1 前趨圖和程序執行2.1.1 前趨圖2.1.2 程序順序執行2.1.3 程序并發執行 2.2 進程的描述2.2.1 進程的定義和特征2.2.2 進程的基本狀態及轉換2.2.3 掛起操作和進程狀態的轉換2.2.4 進程管理中的數據結構 2.3 進程控制…

具身智能之強化學習

在具身智能&#xff08;Embodied AI&#xff09;中&#xff0c;強化學習&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;是一種非常核心的學習方法。它讓智能體&#xff08;agent&#xff09;通過與環境交互&#xff0c;不斷試錯&#xff0c;學習完成任務的策略…

go打印金字塔

需求 打印空心金字塔 解析 // * // * * // * * * // * * * *// 看成由星號、空格組成的矩形&#xff1a; // 1 1 1 0 // 2 3 2 1 // 3 5 3 2 // 4 7 4 3// 層數&#xff1a;n // 每層總元素數&#xff1a;2n-1 // 每星號數&#xff1a;n // 每層空格數&am…

C語言教程(二十二):C 語言頭文件詳解

一、頭文件的定義與形式 頭文件一般具有 .h 擴展名&#xff0c;它主要用來存放函數聲明、宏定義、結構體和共用體的定義、全局變量的聲明等內容。在C語言程序里&#xff0c;可借助 #include 預處理指令把這些頭文件包含到源文件中。 二、頭文件的作用 2.1 函數聲明 頭文件可對…

數據庫day-08

一、實驗名稱和性質 刪除修改數據 驗證 設計 二、實驗目的 1&#xff0e;掌握數據操作-- 刪除、修改&#xff1b; 三、實驗的軟硬件環境要求 硬件環境要求&#xff1a; PC機&#xff08;單機&#xff09; 使用的軟件名稱、版本號以及模塊&#xff1a; Windows 10&#x…

JAVA中Spring全局異常處理@ControllerAdvice解析

一、ControllerAdvice基礎概念 1. 什么是ControllerAdvice&#xff1f; ControllerAdvice是Spring 3.2引入的注解&#xff0c;用于定義全局控制器增強組件&#xff0c;主要功能包括&#xff1a; 全局異常處理&#xff08;最常用&#xff09;全局數據綁定全局數據預處理 2. …

開放平臺架構方案- GraphQL 詳細解釋

GraphQL 詳細解釋 GraphQL 是一種用于 API 的查詢語言&#xff0c;由 Facebook 開發并開源&#xff0c;旨在提供一種更高效、靈活且強大的數據獲取和操作方式。它與傳統的 REST API 有顯著不同&#xff0c;通過類型系統和靈活的查詢能力&#xff0c;解決了 REST 中常見的過度獲…

labview項目文件架構

為了使 LabVIEW 項目更具可擴展性和易于維護&#xff0c;合理規劃和設計項目文件結構是非常重要的。 以下是一些基于行業經驗和最佳實踐的建議&#xff1a; 1. ### 文件夾層次劃分 將不同的功能模塊分開存儲在一個清晰的分層目錄結構中是一個常見的做法。通常情況下&#xff…

Chrome的插件擴展程序安裝目錄是什么?在哪個文件夾?

目錄 前提 直接復制到瀏覽器中打開 Mac下Chrome extension 安裝路徑 最近換了mac pro用起來雖然方便&#xff0c;但是對常用的一些使用方法還是不熟悉。這不為了找到mac上chrome插件的安裝路徑在哪里&#xff0c;花費了不少時間。我想應用有不少像小編一樣剛剛使用mac的小白…

第13講:圖形尺寸與分辨率設置——適配論文版面,打造專業圖稿!

目錄 ?? 為什么這一講重要? ?? 一、先認識幾個關鍵詞 ?? 二、ggsave() 是導出圖的標準方法 ?? 三、尺寸設置技巧:對齊目標期刊 ?? 找到目標期刊的圖形欄寬 ?? 四、多個圖組合導出(與 patchwork 搭配) ?? 五、使用 Cairo / ragg 導出高質量圖 ?? 六…

2025年- H13-Lc120-189.輪轉數組(普通數組)---java版

1.題目描述 2.思路 import java.util.Arrays;public class H189 {public static void main(String[] args) {int[] newArr {1, 2, 3, 4, 5};int[] nums new int[5];System.arraycopy(newArr,0,nums,0,4);System.out.println(Arrays.toString(nums)); } }補充2&#xff1a; 3.…

機器人--相機

教程 畸變和校正 單目和雙目標定 單雙&#xff0c;rgb-d原理 單目相機 只有一個攝像頭的相機。 原理 小孔成像。 缺點 單目相機無法測量物體點的深度信。 因為物體的Z軸坐標系無法測量。 雙目相機 有兩個攝像頭的相機。 用兩個單目相機組成的雙目相機就可以測量深度信…

Go 語言入門:(一) 環境安裝

一、前言 這里不同于其他人的 Go 語言入門&#xff0c;環境安裝我向來注重配置&#xff0c;比如依賴包、緩存的默認目錄。因為前期不弄好&#xff0c;后面要整理又影響這影響那的&#xff0c;所以就干脆寫成文章&#xff0c;方便后期撿起。 二、安裝 1. 安裝包 https://go.…

筆試專題(十二)

文章目錄 主持人調度題解代碼 小紅的ABC題解代碼 不相鄰取數題解代碼 空調遙控題解代碼 主持人調度 題目鏈接 題解 1. 排序 2. 先按左端點的大小進行排序&#xff0c;保證時間是連續的&#xff0c;如果后一個點的左端點大于等于前一個點的右端點就是和法的&#xff0c;否則…

Ansible 守護 Windows 安全(Ansible Safeguards Windows Security)

Ansible 守護 Windows 安全&#xff1a;自動化基線檢查與加固 在當今網絡威脅日益嚴峻的形勢下&#xff0c;保障 Windows 系統安全至關重要。Ansible 作為一款強大的自動化運維工具&#xff0c;可通過自動化腳本實現 Windows 安全基線檢查和加固&#xff0c;大幅提升運維效率并…

深度解析 MyBatis`@TableField(typeHandler = JacksonTypeHandler.class)`:優雅處理復雜數據存儲

一、引言&#xff1a;當Java對象遇見數據庫 在現代應用開發中&#xff0c;我們經常面臨一個關鍵問題&#xff1a;如何將復雜的Java對象&#xff08;如Map、List或自定義POJO&#xff09;優雅地存儲到關系型數據庫中&#xff1f;傳統解決方案需要開發者手動進行序列化和反序列化…