为 Byte Buddy 动态增强已加载类提供安全、可重入的类型缓存机制

byte buddy 在对已加载类(如 author)重复调用 `redefine()` + `injection` 时会抛出 `cannot inject already loaded type` 异常,根本原因是 jvm 不允许向已加载类注入新字段或方法;正确解法是使用 `typecache` 避免重复增强,并配合自定义类加载器实现类型隔离。

在使用 Byte Buddy 对运行时已加载的类(如 pl.edu.wat.testowy.entity.Author)进行动态修改(例如添加字段)时,你遇到的 java.lang.IllegalStateException: Cannot inject already loaded type 并非配置错误,而是 JVM 类加载机制的硬性限制:同一个 ClassLoader 下,一个类名只能对应一个已定义的 Class 实例,且 ClassLoadingStrategy.Default.INJECTION 仅支持对尚未被任何类加载器定义(即未 loadClass 过)的类型执行字节码注入

你的代码中连续两次调用 Reflection.apply("test") 和 Reflection.apply("test2"),导致对同一目标类(如 Author)反复执行:

byteBuddy.redefine(entityDefinition, ...)
        .defineProperty(test, ...)
        .make()
        .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);

而 INJECTION 策略底层依赖 Unsafe.defineAnonymousClass 或 Instrumentation.retransformClasses(取决于 JDK 版本),但它无法对已由系统类加载器加载并初始化的类再次注入新字段——这正是异常的根源。

✅ 正确实践:使用 TypeCache + 自定义 ClassLoader

Byte Buddy 官方推荐的解决方案是引入 类型缓存(TypeCache),确保每个类名仅被增强一次,并通过隔离类加载器加载新类型,避免与原始类冲突:

1. 声明线程安全的 TypeCache

private final TypeCache typeCache = new TypeCache.WithInlineExpunction(
    TypeCache.Sort.WEAK
);

2. 改造 applyEntity:缓存增强结果,避免重复 redefine

public void applyEntity(String fieldName) {
    // 使用全限定名作为 cache key,确保唯一性
    String cacheKey = entityDefinition.getName() + "$enhancedWith" + fieldName;

    try {
        Class enhancedClass = typeCache.findOrInsert(
            ClassLoader.getSystemClassLoader(),
            cacheKey,
            () -> {
                DynamicType.Builder builder = byteBuddy
                    .redefine(entityDefinition, 
                        ClassFileLocator.ForClassLoader.ofSystemLoader())
                    .defineProperty(fieldName, typePool.describe("java.lang.String").resolve());

                return builder.make()
                    .load(new EnhancingClassLoader(), ClassLoadingStrategy.Default.INJECTION)
                    .getLoaded();
            }
        );

        // 更新 entityDefinition 指向新类(注意:原类未改变,此处仅更新元数据引用)
        entityDefinition = new TypeDescription.ForLoadedType(enhancedClass);

    } catch (IOException e) {
        throw new RuntimeException("Failed to enhance Author class", e);
    }
}

3. 实现轻量级 EnhancingClassLoader

private static class EnhancingClassLoader extends ClassLoader {
    public EnhancingClassLoader() {
        super(ClassLoader.getSystemClassLoader());
    }

    @Override
    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 优先委托给父加载器(保证核心类可见)
        try {
            return super.loadClass(name, resolve);
        } catch (ClassNotFoundException ignored) {
            // 增强类由 Byte Buddy 动态生成,不走磁盘路径,直接抛出
            throw new ClassNotFoundException(name);
        }
    }
}
⚠️ 关键说明:EnhancingClassLoader 不负责查找 .class 文件,它仅作为 Byte Buddy 加载增强类的“沙箱容器”。由于 INJECTION 要求目标类未被当前 ClassLoader 加载过,而 EnhancingClassLoader 是全新实例(未加载过 Author),因此可安全注入。

4. 全局一致性保障:禁止多次 apply 同一类

在 Reflection.apply(...) 中应校验是否已增强:

public static void apply(String fieldName) {
    var ref = new Reflection();
    if (!ref.isEntityEnhancedWith(fieldName)) {
        ref.applyEntity(fieldName);
    }
    // 其他 applyRequest/Response 同理...
}

? 总结与最佳实践

  • 不要重复对已加载类使用 INJECTION:ClassLoader.getSystemClassLoader() 加载过的类不可二次注入。
  • 必须使用 TypeCache:防止同一类被多次增强,提升性能并避免状态混乱。
  • 必须使用独立 ClassLoader 实例:如 new EnhancingClassLoader(),确保 Byte Buddy 注入环境干净。
  • 增强后需更新 TypeDescription 引用:用 new TypeDescription.ForLoadedType(enhancedClass) 替换旧描述,保障后续操作基于新结构。
  • ? 注意类隔离副作用:增强类与原始类属于不同 ClassLoader,不能直接强制转换(如 (Author) obj),需通过接口或反射访问新增字段。

通过以上改造,你的 Reflection.apply("test") 和 Reflection.apply("test2") 将各自生成独立增强版本(如 Author$enhancedWithtest, Author$enhancedWithtest2),彻底规避 Cannot inject already loaded type 异常,同时保持代码可维护性与 JVM 安全性。

关于我们

奈瑶·映南科技互联网学院是多元化综合资讯平台,提供网络资讯、运营推广经验、营销引流方法、网站技术、文学艺术范文及好站推荐等内容,覆盖多重需求,助力用户学习提升、便捷查阅,打造实用优质的内容服务平台。

搜索Search

搜索一下,你就知道。