假设我想在运行时创建另一个类可以使用的自定义类。
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();
}
}
}
这很有效。
然而,the ByteBuddy tutorial,建议
由于您一次创建一个动态类型,因此您可能会认为遇到循环依赖关系的可能性很小。但是,动态创建类型可能会触发创建所谓的辅助类型。
这些类型由Byte Buddy自动创建,以提供对您正在创建的动态类型的访问。
因此,我们建议您通过创建特定的ClassLoader来加载动态创建的类,而不是尽可能将它们注入现有的类中。
我不太了解类加载器 - 或者ByteBuddy,但是教程似乎暗示类加载器是按层次排序的。
如果是这样,应该可以将新的类加载器链接到clazz.getClassLoader()
,对吧?
好吧,我对ClassLoadingStrategy.Default.WRAPPER
和ClassLoadingStrategy.Default.CHILD_FIRST
都没有运气。
两者都会导致
created new class redefineconcept.InstanceUser$HelloWorld
java.lang.ClassNotFoundException: redefineconcept.InstanceUser$HelloWorld
让我相信
通常,Java类加载器在尝试直接加载给定名称的类型之前查询其父类ClassLoader。
表示他们仅查询父ClassLoader
但不查询子女。
是这样吗?
是否可以避免在这里使用ClassLoadingStrategy.Default.INJECTION
?
答案 0 :(得分:0)
类加载器(通常)是分层的。如果您使用的是INJECTION
策略,Byte Buddy会通过显式定义类来手动定义类型。根据JVM和类加载器,这可能会触发类加载。
考虑A引用B和B引用A的情况。如果Byte Buddy在B之前注入A,则注入A可能会导致加载尚未在该点注入的B.此时,作为注入目标的类加载器将首先尝试查找B并且未成功尝试使用NoClassDefFoundError
失败。
使用WRAPPER
策略时,Byte Buddy会创建一个新的类加载器,它可以识别这两种类型,并且可以在加载A时查找B,因为不需要注入。
您遇到的问题是由您使用Class.forName(name)
引起的。该方法对调用者敏感,这意味着使用了调用类的类加载器。从您的线程中,这很可能是系统类加载器,它与您之前注入的类加载器相同。
也就是说,通常JVM会延迟加载类型,并且注入不应该对99%的用例造成大问题。