You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ByteBuddy本地正常Docker报错:ClassLoader差异及方案咨询

ByteBuddy本地运行正常,Docker部署后抛出ClassNotFoundException的原因分析

问题背景

ByteBuddy实现在IntelliJ本地运行正常,但打包为Jar部署到Docker容器后抛出ClassNotFoundException。

初始实现(本地正常,Docker报错)

private DynamicType.Loaded<? extends MyClassToRedefine> getLoadedTypeOfResource() {
    if (myClassToRedefineImplLoadedType == null) {
        try (var unloadedResource = new ByteBuddy()
                .subclass(MyClassToRedefine.class)
                .annotateType(newAnnotation1)
                .annotateType(newAnnotation2)
                .annotateType(newAnnotation3)
                .make()) {
         myClassToRedefineImplLoadedType = unloadedResource.load(ClassLoader.getSystemClassLoader());
        }
    }
    return myClassToRedefineImplLoadedType;
}

Docker运行环境

使用以下Dockerfile打包运行:

FROM openjdk:21-jdk

ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]

抛出的异常

Caused by: java.lang.ClassNotFoundException: com.abc.def.MyClassToRedefine
        at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:404)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        ... 73 common frames omitted

修复后的实现(本地和Docker均正常)

private DynamicType.Loaded<? extends MyClassToRedefine> getLoadedTypeOfResource() {
    ByteBuddyAgent.install();
    if (provisionedCustomResourceImplLoadedType == null) {
        try (var unloadedResource = new ByteBuddy()
                .subclass(MyClassToRedefine.class)
                .annotateType(newAnnotation1)
                .annotateType(newAnnotation2)
                .annotateType(newAnnotation3)
                .make()) {
         myClassToRedefineImplLoadedType = unloadedResource.load(MyClassToRedefine.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
        }
    }
    return myClassToRedefineImplLoadedType;
}

疑问解答

1. 为何第一种方案本地正常Docker报错,第二种方案通用?

  • 第一种方案的问题:
    使用ClassLoader.getSystemClassLoader()加载动态子类时,系统类加载器(JDK的AppClassLoader)在Docker环境下无法访问MyClassToRedefine类。Spring Boot Jar运行时,应用类由LaunchedURLClassLoader(Spring Boot自定义类加载器)加载,而AppClassLoader只能加载JDK核心类和classpath根目录下的类,无法穿透Spring Boot Jar访问内部嵌套的类。

    • 本地IntelliJ运行时,类直接放在文件系统的classpath中,AppClassLoader能直接找到目标类,因此加载子类正常。
    • Docker环境下,应用类都在Jar内部,系统类加载器找不到父类,导致动态子类加载失败,抛出ClassNotFoundException。
  • 第二种方案的优势:

    • 用MyClassToRedefine.class.getClassLoader()获取到加载父类的Spring Boot自定义类加载器,动态子类和父类使用同一类加载器,类加载委托机制能正常找到父类。
    • 引入ByteBuddyAgent和ClassReloadingStrategy,通过Java Agent机制实现类加载/重定义,绕过普通类加载器的限制,保证了本地和Docker环境下类加载的一致性。

2. 两种类加载器在本地和Docker环境中的差异

类加载器调用方式本地IntelliJ环境Docker Spring Boot Jar环境
ClassLoader.getSystemClassLoader()为AppClassLoader,可直接访问classpath下所有应用类为AppClassLoader,仅能加载JDK核心类,无法访问Spring Boot Jar内部应用类
MyClassToRedefine.class.getClassLoader()为AppClassLoader(本地类直接在classpath)为LaunchedURLClassLoader(Spring Boot自定义类加载器,负责加载Jar内部应用类)

简单总结:

  • 本地环境下,两种类加载器本质是同一个,都能访问目标类,因此第一种方案可行。
  • Docker环境下,系统类加载器和应用类加载器是不同实例,系统类加载器看不到Jar内部类,导致加载失败;使用父类的类加载器则能正确找到依赖的父类。

内容的提问来源于stack exchange,提问作者Raspberry_Sherbet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 03:23:21