使用ByteBuddy动态修改类报错:Cannot inject already loaded type
问题:ByteBuddy二次调用apply后抛出
Cannot inject already loaded type异常 Exception in thread "main" java.lang.IllegalStateException: Cannot inject already loaded type: class pl.edu.testowy.entity.Author
我想用ByteBuddy给Author类添加多个变量,但第二次调用apply方法时抛出上述异常。我知道这和ClassLoader有关,但修改ClassLoader后没解决问题,相关代码如下:
相关代码
Reflection类
package pl.edu.wat.testowy.reflection; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.pool.TypePool; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static net.bytebuddy.matcher.ElementMatchers.named; public class Reflection { private TypeDescription entityDefinition; private TypeDescription requestDefinition; private TypeDescription responseDefinition; private TypeDescription mapperDefinition; private TypePool typePool; private ByteBuddy byteBuddy; public Reflection() { this.typePool = TypePool.Default.ofSystemLoader(); this.byteBuddy = new ByteBuddy(); this.entityDefinition = typePool.describe("pl.edu.wat.testowy.entity.Author").resolve(); this.requestDefinition = typePool.describe("pl.edu.wat.testowy.dto.AuthorRequest").resolve(); this.responseDefinition = typePool.describe("pl.edu.wat.testowy.dto.AuthorResponse").resolve(); this.mapperDefinition = typePool.describe("pl.edu.wat.testowy.mapper.AuthorMapper").resolve(); } FieldInformation fieldInformation = new FieldInformation(); FieldInformation2 fieldInformation2 = new FieldInformation2(); //nie beda to pola final bo one beda sie zmienialy public static void apply(String test){ var ref = new Reflection(); ref.applyEntity(test); ref.applyRequest(test); ref.applyResponse(test); ref.applyAuthorMapper(test); } public List<String> getFieldNames() { FieldInformation2.readJson("fields2.json"); List<FieldInformation2> fields = FieldInformation2.getFields(); List<String> fieldNames = new ArrayList<>(); fields.forEach(f -> fieldNames.add(f.getFieldName())); return fieldNames; } private void applyAuthorMapper(String test) {//musimy wyciagnac mappera DynamicType.Builder <Object> builder = byteBuddy .redefine(mapperDefinition, ClassFileLocator.ForClassLoader.ofSystemLoader()) .method(named("fillAuthorRequest")) .intercept(MethodCall.invoke(setterAuthorEntity(test)) .onArgument(0) .withMethodCall(MethodCall .invoke(getterRequest(test)) .onArgument(1))) .method(named("fillAuthor")) .intercept(MethodCall.invoke(setterAuthorResponse(test)) .onArgument(0) .withMethodCall(MethodCall .invoke(getterEntity(test)) .onArgument(1))); try(var unloadedAuthor = builder.make()){ mapperDefinition = unloadedAuthor.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION) .getTypeDescription(); } catch(IOException e){ throw new RuntimeException(); } } private MethodDescription getterEntity(String test){ return entityDefinition .getDeclaredMethods() .filter(ElementMatchers.isGetter(test)) .stream() .findFirst() .orElseThrow(); } private MethodDescription setterAuthorResponse(String test) { return responseDefinition .getDeclaredMethods() .filter(ElementMatchers.isSetter(test)) .stream() .findFirst() .orElseThrow(); } private MethodDescription getterRequest(String test) { return requestDefinition .getDeclaredMethods() .filter(ElementMatchers.isGetter(test)) .stream() .findFirst() .orElseThrow(); } private MethodDescription setterAuthorEntity(String test) { return entityDefinition .getDeclaredMethods() .filter(ElementMatchers.isSetter(test)) .stream() .findFirst() .orElseThrow(); } private void applyResponse(String test) { DynamicType.Builder <Object> builder = byteBuddy .redefine(responseDefinition, ClassFileLocator.ForClassLoader.ofSystemLoader()) .defineProperty(test,typePool.describe("java.lang.String").resolve()); try(var unloadedAuthor = builder.make()){ responseDefinition = unloadedAuthor.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION) .getTypeDescription(); } catch(IOException e){ throw new RuntimeException(); } } private void applyRequest(String test) { DynamicType.Builder <Object> builder = byteBuddy .redefine(requestDefinition, ClassFileLocator.ForClassLoader.ofSystemLoader()) .defineProperty(test,typePool.describe("java.lang.String").resolve()); try(var unloadedAuthor = builder.make()){ requestDefinition = unloadedAuthor.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION) .getTypeDescription(); } catch(IOException e){ throw new RuntimeException(); } } public void applyEntity(String test) { DynamicType.Builder <Object> builder = byteBuddy .redefine(entityDefinition, ClassFileLocator.ForClassLoader.ofSystemLoader()) .defineProperty(test,typePool.describe("java.lang.String").resolve()); try(var unloadedAuthor = builder.make()){ entityDefinition = unloadedAuthor.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION) .getTypeDescription(); } catch(IOException e){ throw new RuntimeException(); } } }
TestowyApplication类
package pl.edu.wat.testowy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import pl.edu.wat.testowy.reflection.Reflection; @SpringBootApplication public class TestowyApplication { public static void main(String[] args) { //tak jak tu korzystam z mechanizmu refleksji tak wystarczy utworzyc zalozmy 10 obiektow, gdzie podajemy nazwe i typ zmiennej Reflection.apply("test"); Reflection.apply("test2"); SpringApplication.run(TestowyApplication.class, args); } }
解决方案
核心原因
每次调用Reflection.apply()都会新建Reflection实例,且每次都用ClassLoadingStrategy.Default.INJECTION尝试注入已被JVM加载的类。JVM不允许修改已加载类的结构,INJECTION策略仅适用于加载未被加载过的类。
修复方案1:一次性处理所有字段
将需要添加的字段收集后一次性完成修改,避免多次触发类加载:
// 修改Reflection的apply方法,接收字段列表 public static void apply(List<String> fields) { var ref = new Reflection(); fields.forEach(field -> { ref.applyEntity(field); ref.applyRequest(field); ref.applyResponse(field); ref.applyAuthorMapper(field); }); } // 修改main方法,一次性传入所有字段 public static void main(String[] args) { Reflection.apply(List.of("test", "test2")); SpringApplication.run(TestowyApplication.class, args); }
修复方案2:使用自定义类加载器
如果必须分多次修改,可使用自定义类加载器加载修改后的类(不同类加载器加载的同名类在JVM中视为不同类型):
// 在加载类时替换为自定义类加载器和WRAPPER策略 ClassLoader customClassLoader = new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader()); mapperDefinition = unloadedAuthor.load(customClassLoader, ClassLoadingStrategy.Default.WRAPPER) .getTypeDescription();
注意:后续代码需统一使用该自定义类加载器访问修改后的类,否则会出现类型不匹配问题。
额外优化点
将Reflection中的entityDefinition等字段改为静态,或在实例间传递修改后的TypeDescription,确保每次修改都是基于上一次修改后的类结构,而非重新读取原始类信息。
内容的提问来源于stack exchange,提问作者Adamero
相关产品推荐
相关产品推荐

