Java 如何以正确的方式加载此类?

Java 如何以正确的方式加载此类?,java,classloader,noclassdeffounderror,bytecode,byte-buddy,Java,Classloader,Noclassdeffounderror,Bytecode,Byte Buddy,好的,我有一节课 class A{ public D d = new B$0(); public void foo(){ B$0 b; try{ b = (B$0)this.d; }catch(ClassCastException e){ this.d = d.move(this); //actual implementation uses a CAS to make sure i

好的,我有一节课

class A{
    public D d = new B$0();

    public void foo(){
        B$0 b;
        try{
            b = (B$0)this.d;
        }catch(ClassCastException e){
             this.d = d.move(this); //actual implementation uses a CAS to make sure it's only replaced once
             throw new RepeatThisMethodException();
        }
        //do something with b here
    }
}
其中,
RepeatThisMethodException
由后续的一些代码处理

abstract class D{
    public abstract D move(Object o);
}

我现在创建一个新类

class B$1 extends D{
    public D move(Object o){
        return B$0.moveThis((A)o);
    }
}
并使用ByteBuddy加载它

    DynamicType.Builder builder = byteBuddy
            .subclass(D.class)
            .name("B$1")
            ;

    DynamicType.Unloaded newClass = builder.make();
    byte[] rawBytecode = newClass.getBytes();
    byte[] finishedBytecode = MyASMVisitor.addMethods(rawBytecode);

    Class b0 = Class.forName("B$0");
    ClassLoadingStrategy.Default.INJECTION.load(b0.getClassLoader(),
            Collections.singletonMap(newClass.getTypeDescription(), finishedBytecode));
(注意,我正在使用
B$0.class.getClassloader()
加载
B$1

move
方法
MyASMVisitor
添加的字节码如下所示:

public Method move:"(Ljava/lang/Object;)LD;"
    stack 1 locals 2
{
        aload_1;
        checkcast   class A;
        invokestatic    Method B$0.moveThis:"(LA;)LD;";
        areturn;
}
现在加载了
B$1
,我重新插入
B$0
s.t。它可以处理新类

class B$0 extends D{
    public static D moveThis(A a){
        if(a.d instanceof B$1) throw new RepeatThisMethodException();
        if(a.d instanceof B$0) return new B$1();
        throw new Error();
    }

    public D move(Object o){
        return moveThis((A)o);
    }|
}
并使用

private void redefineClass(String classname, byte[] bytecode) {
    Class clazz;
    try{
        clazz = Class.forName(classname);
    }catch(ClassNotFoundException e){
        throw new RuntimeException(e);
    }

    ClassReloadingStrategy s = ClassReloadingStrategy.fromInstalledAgent();
    s.load(clazz.getClassLoader(),
            Collections.singletonMap((TypeDescription)new TypeDescription.ForLoadedType(clazz), bytecode));
}
因此
B$0
B$0.class.getClassLoader()
重新加载

既然
B$1
已经存在并且可以处理,我让
A
知道从现在起它应该使用新类

class A{
    public D d = new B$1();

    public void foo(){
        B$1 b;
        try{
            b = (B$1)this.d;
        }catch(ClassCastException e){
             this.d = d.move(this); //actual implementation uses a CAS to make sure it's only replaced once
             throw new RepeatThisMethodException();
        }
        //do something with b here
    }
}
并使用相同的
重定义类
方法重新加载它(因此
A
通过
A.class.getClassLoader()
重新加载)

实际上,
A
的新实例将从一开始就使用
B$1
,而现有实例将调用
B.move(this)
,后者将依次调用
B$0.moveThis((A)o)
(在不能使用
this
的情况下)

目前看来,这似乎有效

现在的问题是,我们需要更新所有使用
B
版本的类。显然,我们无法同时重新加载所有类,因此有些类会更早,有些类会晚

假设我们有一个类
G
,它使用
a
,因此,它的
a.d

A
已重新加载,
G
尚未加载。因此
A
上的某些方法(或
A
的任何其他已重新加载的客户端)可能已经触发了
移动
,而
G
仍试图转换为
B$0

那很好

如果
G
使用
A
并未能将
A.d
转换为它期望的版本,它将调用
A.d.move(A)
,后者反过来调用
B$0。moveThis((A)A)

那么,

if(a.d instanceof B$1) throw new RepeatThisMethodException();
在我们处理
B$0
中的代码时,确保
G
在字节码被重新加载并且它知道
B$1
之前无法取得进展

或者,如果
B$1
可以调用
B$0。移动此

相反,我们得到了

Exception in thread "MyT1" java.lang.NoClassDefFoundError: A
    at B$1.move(Unknown Source)
好吧,这是不幸的。让我们看看是否可以通过将
对象o
的强制转换移动到
B$0来避免这个错误。移动这个

Exception in thread "MyT1" java.lang.NoClassDefFoundError: B$0
    at B$1.move(Unknown Source)
不,看起来不像

如何加载
B$1
s.t.。它至少可以访问
B$0
,并且
B$0
A
(以及
A
的任何客户端最终)都可以访问它

注意

任何类型的解决方案都需要支持向上投射

例如,假设我有
D:>B:>C
,我们使用
bb=newc()
(或者将
C
的一个实例传递给一个需要a
B
或…)的方法,那么
B.move(B)
仍然必须调用
C$0.moveThis((C)B)

更新(谢谢,霍尔格)

这些问题似乎与现有类的重新定义无关

    Class b0 = Class.forName("B$0");
    ClassLoadingStrategy.Default.INJECTION.load(b0.getClassLoader(),
            Collections.singletonMap(newClass.getTypeDescription(), finishedBytecode));

    try {
        Class c = Class.forName("B$1");
        Object instance = c.newInstance();
        c.getMethod("move", Object.class).invoke(instance, new Object());
    } catch (Exception e) {
        e.printStackTrace();
    }
在重新加载任何其他类之前调用
B$1.move()
,实际上足以触发
NoClassDefFoundError

更新


当我为重新加载类而打印
clazz.getClassLoader()
为新类打印
b0.getClassLoader()
时,我总是得到相同的
sun.misc.Launcher$AppClassLoader

实例。这在实际的代码生成中是一个问题,正如在中所讨论的。

我们不能同时重新加载它们-为什么不呢?是你的代码显式地使用了
集合。singletonMap(…)
在API接受任意
映射的地方
Map
s.@Holger我不需要以某种方式指定他们应该使用哪些类加载器吗?(使用
A.class.getClassLoader()重新加载
A
B
使用
B.class.getClassLoader())
,…)或者我可以在这个用例中使用任意类加载器吗?对于使用代理的重新传输,类加载器参数对于已经加载的类并不重要,因为这些类已经有一个永远不会更改的定义加载器。只有当映射还包含未解析类型的类型描述时,该方法才会尝试通过指定的类加载器。@Holger,恐怕没有成功。同样的错误。您的问题似乎与重新定义无关。只需在加载
B$1
之后(甚至在尝试重新定义其他类之前),通过反射尝试调用
move
方法即可。在解决
A
B$0
问题时,它是否起作用,或者已经显示出相同的问题?
    Class b0 = Class.forName("B$0");
    ClassLoadingStrategy.Default.INJECTION.load(b0.getClassLoader(),
            Collections.singletonMap(newClass.getTypeDescription(), finishedBytecode));

    try {
        Class c = Class.forName("B$1");
        Object instance = c.newInstance();
        c.getMethod("move", Object.class).invoke(instance, new Object());
    } catch (Exception e) {
        e.printStackTrace();
    }