java - 加载检测类时避免注入(inject)

标签 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 ,建议

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.

我不太了解类加载器——或者 ByteBuddy,就这一点而言——但教程似乎表明类加载器是按层次排序的。

如果是这样,应该可以将新的类加载器链接到 clazz.getClassLoader(),对吧?

好吧,无论是 ClassLoadingStrategy.Default.WRAPPER 还是 ClassLoadingStrategy.Default.CHILD_FIRST,我都没有这样的运气。

两者都会导致

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

这让我相信

Normally, Java class loaders query their parent ClassLoader before attempting to directly load a type of a given name.

意味着它们查询父ClassLoader,而不查询子级。

是这样吗?

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

最佳答案

类加载器(通常)是分层的。如果您使用INJECTION 策略,Byte Buddy 通过显式定义类来手动定义类型。根据 JVM 和类加载器,这可能会触发类加载。

考虑 A 引用 B 并且 B 引用 A 的情况。如果 Byte Buddy 在 B 之前注入(inject) A,则 A 的注入(inject)可能会导致加载此时尚未注入(inject)的 B。此时,作为注入(inject)目标的类加载器将过早且不成功地尝试查找 B,并因 NoClassDefFoundError 失败。

当使用WRAPPER策略时,Byte Buddy创建一个新的类加载器,它可以识别这两种类型,并且可以在加载A时查找B,因为不需要注入(inject)。

您遇到的问题是由您使用Class.forName(name)引起的。此方法对调用者敏感,这意味着使用调用类的类加载器。从您的线程来看,这很可能是系统类加载器,它与您之前注入(inject)的类加载器相同。

也就是说,JVM 通常会延迟加载类型,并且对于 99% 的用例来说,注入(inject)不会造成大问题。

关于java - 加载检测类时避免注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47415398/

相关文章:

java - 将多个同名文件合并为一个文件

java - 如果键已经存在,则递归地增加相同的嵌套映射值

java - NoClassDefFoundError 在 Tomcat 中但在动态创建的 Java 类的独立应用程序中没有

java - 致命异常 :Async Task #1

java - Java-Process 可以删除加载的 JAR 吗?

java - SLF4J 的 “dynamic binding” 功能什么时候适合使用?

.net - 将属性与 .net 中的代码生成属性相关联

types - 如何根据目标语言更改 Telosys 默认类型?

java - 根据类中已有字段自动生成Java代码

java - 明智的做法是,在引发异常的条件之后添加 else 是否更好?