加载检测类时避免注入

时间:2017-11-21 14:21:50

标签: java classloader code-generation instrumentation byte-buddy

假设我想在运行时创建另一个类可以使用的自定义类。

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.WRAPPERClassLoadingStrategy.Default.CHILD_FIRST都没有运气。

两者都会导致

created new class redefineconcept.InstanceUser$HelloWorld
java.lang.ClassNotFoundException: redefineconcept.InstanceUser$HelloWorld

让我相信

  

通常,Java类加载器在尝试直接加载给定名称的类型之前查询其父类ClassLoader。

表示他们查询父ClassLoader但不查询子女。

是这样吗?

是否可以避免在这里使用ClassLoadingStrategy.Default.INJECTION

1 个答案:

答案 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%的用例造成大问题。