【问题标题】:Avoiding Injection when loading instrumented classes加载检测类时避免注入
【发布时间】:2018-05-05 01:15:38
【问题描述】:

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

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 类加载器会在尝试直接加载给定名称的类型之前查询其父类加载器。

意味着他们查询父 ClassLoaders 而不是子。

是这样吗?

这里有没有可能避免使用ClassLoadingStrategy.Default.INJECTION

【问题讨论】:

  • 既然你用的是Class.forName(cn),为什么还要坚持改变类加载器的解析策略呢?使用自定义类加载器,传递它并将Class.forName(cn) 替换为customClassLoader.loadClass(cn)...

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


【解决方案1】:

类加载器(通常)是分层的。如果您使用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% 的所有用例而言,注入不应成为大问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-10
    • 1970-01-01
    • 2017-12-02
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多