byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

How to redefine dynamically created classes?

Open cilfm opened this issue 1 year ago • 5 comments

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]

cilfm avatar Aug 19 '24 08:08 cilfm

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.

raphw avatar Aug 19 '24 21:08 raphw

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?

cilfm avatar Aug 22 '24 01:08 cilfm

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

raphw avatar Aug 22 '24 21:08 raphw

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>

cilfm avatar Aug 23 '24 11:08 cilfm

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.

raphw avatar Aug 23 '24 11:08 raphw