成都多享网站建设公司,网站集约化建设意见,手机网站制作案例,哈密建设厅网站目录 一、问题描述二、原因分析三、解决方案1#xff08;推荐#xff09;#xff1a;获取线程上下文中的类加载器扩展 四、解决方案2#xff1a;自定义SpringBoot类加载器 一、问题描述
在使用Byte-Buddy中的TypePool对类进行扩展后#xff0c;在本地开发集成环境#x… 目录 一、问题描述二、原因分析三、解决方案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.ContextClassLoaderjdk.internal.loader.ClassLoaders$AppClassLoader
SystemClassLoaderjdk.internal.loader.ClassLoaders$AppClassLoader
PlatformClassLoaderjdk.internal.loader.ClassLoaders$PlatformClassLoader通过SpringBoot打包成Jar运行Jar后打印结果如下
Thread.CurrentThread.ContextClassLoaderorg.springframework.boot.loader.launch.LaunchedClassLoader
SystemClassLoaderjdk.internal.loader.ClassLoaders$AppClassLoader
PlatformClassLoaderjdk.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而是普通的redefine、rebase等操作此时是可以直接获取到被扩展的类的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 hrefhttps://github.com/raphw/byte-buddy/issues/1470#issuecomment-1617556513https://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();ListClassFileLocator classFileLocators new ArrayListClassFileLocator();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可以作为一个备选方案。