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.mzph.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;传统解决方案需要开发者手动进行序列化和反序列化…