Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to redefine dynamically created classes? #1698

Closed
cilfm opened this issue Aug 19, 2024 · 5 comments
Closed

How to redefine dynamically created classes? #1698

cilfm opened this issue Aug 19, 2024 · 5 comments
Assignees
Labels
Milestone

Comments

@cilfm
Copy link

cilfm commented Aug 19, 2024

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
Copy link
Owner

raphw commented Aug 19, 2024

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 raphw self-assigned this Aug 19, 2024
@raphw raphw added the question label Aug 19, 2024
@raphw raphw added this to the 1.14.19 milestone Aug 19, 2024
@cilfm
Copy link
Author

cilfm commented Aug 22, 2024

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
Copy link
Owner

raphw commented Aug 22, 2024

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
Copy link
Author

cilfm commented Aug 23, 2024

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
Copy link
Owner

raphw commented Aug 23, 2024

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 raphw closed this as completed Sep 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants