raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.22k stars 799 forks source link

How to redefine dynamically created classes? #1698

Open cilfm opened 3 weeks ago

cilfm commented 3 weeks ago

byte-buddy version:1.14.19

Firstly, I have successfully dynamically defined a class

com.pf.test.entity.TestEntity

Class<?> entityClass = new ByteBuddy()
        .subclass(BaseEntity.class)
        .name("com.pf.test.entity.TestEntity")
        .defineProperty("name", String.class)
        .defineProperty("cityCode", String.class)
        .make()
        .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
        .getLoaded();

Now I want to add another property to this class

Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
method.setAccessible(true);
Class<?> oldEntityClass = (Class<?>) method.invoke(this.getClass().getClassLoader(), "com.pf.test.entity.TestEntity");

ByteBuddyAgent.install();
Loaded<?> entityClass = new ByteBuddy()
        .redefine(oldEntityClass)
        .name(tableBean.getEntity().getClassName())
        .defineProperty("city_level", Integer.class)
        .make()
        .load(String.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

bug I get the following exception

java.lang.IllegalStateException: Could not locate class file for com.pf.test.entity.TestEntity
    at net.bytebuddy.dynamic.ClassFileLocator$Resolution$Illegal.resolve(ClassFileLocator.java:118) ~[byte-buddy-1.11.22.jar:na]
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3913) ~[byte-buddy-1.11.22.jar:na]
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2192) ~[byte-buddy-1.11.22.jar:na]
    at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224) ~[byte-buddy-1.11.22.jar:na]
    at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123) ~[byte-buddy-1.11.22.jar:na]
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3658) ~[byte-buddy-1.11.22.jar:na]
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:3896) ~[byte-buddy-1.11.22.jar:na]
raphw commented 3 weeks ago

This is not possible with injection as the class loader cannot be patched to resolve the class file. You could use ClassLoadingStrategy.Default.WRAPPER_PERSISTENT instead.

Alternatively, you can use an AgentBuilder and trigger a retransformation where the JVM will resolve the class file.

cilfm commented 3 weeks ago

This is not possible with injection as the class loader cannot be patched to resolve the class file. You could use ClassLoadingStrategy.Default.WRAPPER_PERSISTENT instead.

Alternatively, you can use an AgentBuilder and trigger a retransformation where the JVM will resolve the class file.

Hello, I used ClassLoadingStrategy.Default.WRAPPER_PERSISTENT to load the class when creating it, but encountered an error when using this class:

Caused by: java.lang.ClassNotFoundException: com.pf.test.entity.TestEntity
    at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:396) ~[byte-buddy-1.11.22.jar:na]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_271]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_271]
    at java.lang.Class.forName0(Native Method) ~[na:1.8.0_271]
    at java.lang.Class.forName(Class.java:348) ~[na:1.8.0_271]
    at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114) ~[na:1.8.0_271]
    ... 128 common frames omitted

Testing has found that Class.forName("com.pf.test.entity.TestEntity", false, this.getClass().getClassLoader()) will all report ClassNotFoundException,when Load class by .load(this.getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT) How should I solve it?

raphw commented 3 weeks ago

Is this the class that you defined? That should not happen. Can you try to create a reproducer? Likely there is a mixup somewhere.

cilfm commented 3 weeks ago

Is this the class that you defined? That should not happen. Can you try to create a reproducer? Likely there is a mixup somewhere.

Yes, that class was defined by me. Sorry, I don't understand how to create a reproducer. I put my code below, can you help me?

package com.pf.test;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

public class ByteBuddyTest {

    public static void main(String[] args) {
        Class<?> entity = createEntity();
        System.out.println("entity class is created");

        Class<?> service = createService(entity);
        System.out.println("service interface is created");

        Class<?> mapper = createMapper(entity);
        System.out.println("mapper interface is created");

        createServiceImpl(mapper, entity, service);
        System.out.println("serviceImpl class is created");
    }

    private static Class<?> createEntity() {
        return new ByteBuddy().subclass(Object.class).name("com.pf.test.entity.TestEntity")
                .annotateType(AnnotationDescription.Builder.ofType(TableName.class).define("value", "test").build())
                .defineProperty("name", String.class).defineProperty("cityCode", String.class)
                .defineProperty("cityLevel", Integer.class).make()
                .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded();
    }

    private static Class<?> createService(Class<?> entityClass) {
        return new ByteBuddy()
                .makeInterface(TypeDescription.Generic.Builder.parameterizedType(IService.class, entityClass).build())
                .name("com.pf.test.service.ITbDicService")
                .defineMethod("testMethod",
                        TypeDescription.Generic.Builder
                                .parameterizedType(TypeDescription.ForLoadedType.of(List.class),
                                        TypeDescription.Generic.Builder
                                                .parameterizedType(Map.class, String.class, Object.class).build())
                                .build(),
                        Visibility.PUBLIC)
                .withParameter(TypeDescription.Generic.Builder.parameterizedType(Map.class, String.class, Object.class)
                        .build())
                .withoutCode().make()
                .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded();
    }

    private static Class<?> createMapper(Class<?> entityClass) {
        return new ByteBuddy()
                .makeInterface(TypeDescription.Generic.Builder.parameterizedType(BaseMapper.class, entityClass).build())
                .name("com.pf.test.mapper.TbDicMapper")
                .annotateType(AnnotationDescription.Builder.ofType(Mapper.class).build())
                .defineMethod("testMethod",
                        TypeDescription.Generic.Builder
                                .parameterizedType(TypeDescription.ForLoadedType.of(List.class),
                                        TypeDescription.Generic.Builder
                                                .parameterizedType(Map.class, String.class, Object.class).build())
                                .build(),
                        Visibility.PUBLIC)
                .withParameter(TypeDescription.Generic.Builder.parameterizedType(Map.class, String.class, Object.class)
                        .build())
                .annotateParameter(AnnotationDescription.Builder.ofType(Param.class).define("value", "querys").build())
                .withoutCode()
                .annotateMethod(AnnotationDescription.Builder.ofType(Select.class)
                        .defineArray("value", new String[] { "select * from test" }).build())
                .make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
                .getLoaded();
    }

    private static Class<?> createServiceImpl(Class<?> mapperClass, Class<?> entityClass, Class<?> serviceClass) {
        return new ByteBuddy()
                .subclass(TypeDescription.Generic.Builder.parameterizedType(ServiceImpl.class, mapperClass, entityClass)
                        .build())
                .implement(serviceClass).name("com.pf.test.service.impl.TbDicServiceImpl")
                .annotateType(AnnotationDescription.Builder.ofType(Service.class).build())
                .method(ElementMatchers.named("testMethod")).intercept(MethodDelegation.toField("baseMapper")).make()
                .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded();
    }
}

Here is the output content

entity class is created
service interface is created
mapper interface is created
Exception in thread "main" java.lang.TypeNotPresentException: Type com.pf.test.entity.TestEntity not present
    at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117)
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125)
    at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
    at sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
    at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
    at sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:108)
    at java.lang.Class.getGenericInterfaces(Class.java:913)
    at net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes$TypeProjection.resolve(TypeList.java:823)
    at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6301)
    at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithResolvedErasure.resolve(TypeDescription.java:6953)
    at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6301)
    at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:746)
    at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.analyze(MethodGraph.java:710)
    at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:746)
    at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.compile(MethodGraph.java:668)
    at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$AbstractBase.compile(MethodGraph.java:519)
    at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:472)
    at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.toTypeWriter(SubclassDynamicTypeBuilder.java:212)
    at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.toTypeWriter(SubclassDynamicTypeBuilder.java:203)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4055)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3739)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:3991)
    at com.aecc.plugins.ByteBuddyTest.createServiceImpl(ByteBuddyTest.java:90)
    at com.aecc.plugins.ByteBuddyTest.main(ByteBuddyTest.java:33)
Caused by: java.lang.ClassNotFoundException: com.pf.test.entity.TestEntity
    at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:404)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114)
    ... 24 more

Project dependencies

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
  <groupId>net.bytebuddy</groupId>
  <artifactId>byte-buddy</artifactId>
  <version>1.14.19</version>
</dependency>
<dependency>
  <groupId>net.bytebuddy</groupId>
  <artifactId>byte-buddy-agent</artifactId>
  <version>1.14.19</version>
</dependency>
raphw commented 3 weeks ago

The problem is that you create wrappers that all sit on top of the system class loader. The generated classes will be visible only within their own loader and children. So you will have to present any generated class's class loader to the next generation.

To avoid generating so many class loaders, you can choose to not seal them. Have a look at ClassLoadingStrategy for this. Ideally, you should seal the loader after you generated all classes.