Reputation: 8182
Let's say I want to create a custom class at runtime that another class can make use of.
package redefineconcept;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import java.lang.reflect.InvocationTargetException;
public class LoadingTest {
public static final String HelloWorldTag = "$HelloWorld";
public static void main(String[] args){
new LoadingTest().run();
}
private void run(){
InstanceUser u = new InstanceUser();
u.start();
Class <?> createdClass = createAndLoadFor(InstanceUser.class);
System.out.println(String.format("created new class %s", createdClass.getName()));
InstanceUser.canAccess = true;
try {
u.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private Class<?> createAndLoadFor(Class<?> clazz){
ByteBuddy byteBuddy = new ByteBuddy();
String newClassName = clazz.getName() + LoadingTest.HelloWorldTag;
DynamicType.Builder builder = byteBuddy
.subclass(Object.class)
.name(newClassName)
;
DynamicType.Unloaded<?> newType = builder.make();
return newType
.load(clazz.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
}
}
class InstanceUser extends Thread{
public static volatile boolean canAccess = false;
Object instance;
@Override
public void run() {
while(!canAccess){}
String cn = this.getClass().getName() + LoadingTest.HelloWorldTag;
Class clazz;
try{
clazz = Class.forName(cn);
}catch(ClassNotFoundException e){
e.printStackTrace();
throw new RuntimeException();
}
try{
instance = clazz.getConstructor().newInstance();
}catch(NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e){
e.printStackTrace();
throw new RuntimeException();
}
}
}
This works.
However, the ByteBuddy tutorial, suggests
You might consider the chance of encountering circular dependencies to be of minor relevance since you are creating one dynamic type at a time. However, the dynamic creation of a type might trigger the creation of so-called auxiliary types.
These types are created by Byte Buddy automatically to provide access to the dynamic type you are creating.
because of this, we recommend you to load dynamically created classes by creating a specific ClassLoader instead of injecting them into an existing one, whenever possible.
I don't know terribly much about classloaders -- or ByteBuddy, for that matter -- but the tutorial seems to suggest that classloaders are hierachically ordered.
If so, it should be possbible to chain the new class loader to clazz.getClassLoader()
, right?
Well, I've had no such luck with neither ClassLoadingStrategy.Default.WRAPPER
nor ClassLoadingStrategy.Default.CHILD_FIRST
.
Both result in
created new class redefineconcept.InstanceUser$HelloWorld
java.lang.ClassNotFoundException: redefineconcept.InstanceUser$HelloWorld
which led me to believe that
Normally, Java class loaders query their parent ClassLoader before attempting to directly load a type of a given name.
means they only query the parent ClassLoader
s but not the children.
Is that so?
And is it at all possible to avoid using ClassLoadingStrategy.Default.INJECTION
, here?
Upvotes: 2
Views: 790
Reputation: 44032
Class loaders are (normally) hierarchical. If you are using the INJECTION
strategy, Byte Buddy manually defines type by type by explicitly defining the classes. Depending on the JVM and class loader, this might trigger a class loading.
Consider a situation where A references B and B references A. If Byte Buddy injects A before B, the injection of A might cause a loading of B which is not yet injected at that point. At this point, the class loader that is the target of the injection will prematurly and unsuccessfully try to look up B and fail with a NoClassDefFoundError
.
When using the WRAPPER
strategy, Byte Buddy creates a new class loader that is aware of both types and can look up B when A is loaded as no injection is required.
The problem you encounter is caused by your use of Class.forName(name)
. This method is caller sensitive meaning that the calling class's class loader is used. From your thread, this will most likely be the system class loader which is the same class loader you injected before.
That said, typically a JVM is loading types lazily and injection should not render a big problem for 99% of all use cases.
Upvotes: 2