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

ByteBuddy加载动态类时抛出已加载类ClassNotFoundException问题

问题结论

ByteBuddy 原生支持动态生成类持有其他动态生成类实例引用的场景,你当前抛出ClassNotFoundException完全是代码实现逻辑错误导致的,和框架能力无关。

根因分析

你代码里有两个致命逻辑错误:

  • 类结构定义顺序错误:你是先配置构造函数的字段赋值拦截逻辑,之后才调用defineField给类添加私有字段。ByteBuddy的链式Builder是按调用顺序生成字节码结构的,你配置构造函数拦截时,目标字段还没被注册到类的结构元数据里,生成的字节码里字段引用的类型解析会出现异常。
  • 类加载器上下文隔离问题:你默认调用的unloadedType.load(SYSTEM_CLASS_LOADER)方法,用的是ByteBuddy默认的WRAPPER类加载策略,这个策略不会把字节码直接注入到你传入的系统类加载器,而是每次调用都会新建一个独立的、以系统类加载器为父级的子包装类加载器来加载新生成的类。也就是说你先加载B类时用了一个独立的子加载器,后续加载A类时又新建了另一个完全独立的子加载器,新加载器看不到之前另一个子加载器里加载的B类,自然会抛出B类找不到的异常。
  • 额外提示:你当前在构造A类的拦截逻辑时,提前实例化了全局唯一的B实例,直接把这个实例硬编码到字节码中赋值给A的字段,会导致所有A类实例共享同一个B对象,如果你的业务预期是每个A实例持有独立的B对象,这个写法也需要调整。
修正方案

按以下步骤调整代码即可:

  1. 调整Builder的调用顺序:先定义所有字段,再配置构造函数的拦截逻辑,保证配置字段赋值时,字段已经完成声明。
  2. 统一类加载上下文:要么把所有动态生成的Child类和Parent类的字节码收集到一起,一次性用同一个类加载策略加载;要么改用支持往目标类加载器注入字节码的加载策略(Java 8环境可直接用INJECTION策略,高版本Java用CHILD_FIRST策略并复用同一个加载器实例加载所有动态类)。

修正后的核心代码参考:

private static final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader();
// 复用同一个类加载策略,避免多加载器隔离
private static final ClassLoadingStrategy<ClassLoader> LOAD_STRATEGY = ClassLoadingStrategy.Default.INJECTION;

public Class<? extends Parent> generateParentClass(ParentConfig config) throws Exception {
    Map<String, Class<? extends Child>> fieldMap = config.getChildren().stream()
            .collect(Collectors.toMap(ChildConfig::getName, this::generateChildClass));

    // 第一步:先定义所有字段,不要先写构造函数逻辑
    DynamicType.Builder<Parent> typeBuilder = new ByteBuddy()
            .subclass(Parent.class)
            .name(Parent.class.getPackageName() + "." + config.getType())
            .annotateType(Parent.class.getAnnotations());

    for(Map.Entry<String, Class<? extends Child>> entry : fieldMap.entrySet()) {
        typeBuilder = typeBuilder.defineField(entry.getKey(), entry.getValue(), Modifier.PRIVATE);
    }

    // 第二步:字段定义完成后,再配置构造函数拦截逻辑
    Implementation.Composable constructorInterceptor = MethodCall.invoke(Parent.class.getConstructor());
    for(Map.Entry<String, Class<? extends Child>> entry : fieldMap.entrySet()) {
        Child child = entry.getValue().getDeclaredConstructor().newInstance();
        constructorInterceptor = constructorInterceptor.andThen(FieldAccessor.ofField(entry.getKey()).setsReference(child));
    }
    typeBuilder = typeBuilder.constructor(any()).intercept(constructorInterceptor);

    DynamicType.Unloaded<Parent> unloadedType = typeBuilder.make();
    // 用统一的加载策略加载,保证能找到同上下文加载的Child类
    return unloadedType.load(SYSTEM_CLASS_LOADER, LOAD_STRATEGY).getLoaded();
}

private Class<? extends Child> generateChildClass(ChildConfig config) {
    DynamicType.Builder<Child> typeBuilder = new ByteBuddy()
            .subclass(Child.class)
            .name(Child.class.getPackageName() + "." + config.getName())
            .annotateType(Child.class.getAnnotations());

    for(Attribute attribute : config.getAttributes()) {
        typeBuilder = typeBuilder.defineField(attribute.getName(), attribute.getTypeClass(), Modifier.PRIVATE);
    }

    DynamicType.Unloaded<Child> unloadedType = typeBuilder.make();
    // 用同一个加载策略加载到系统类加载器,保证后续Parent类能找到该类型
    return unloadedType.load(SYSTEM_CLASS_LOADER, LOAD_STRATEGY).getLoaded();
}

如果你使用Java 9及以上版本,INJECTION策略受模块访问限制无法使用,可以替换为ClassLoadingStrategy.Default.CHILD_FIRST,注意把所有动态类都通过同一个包装类加载器加载即可,不要每次load都新建加载器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:42:31