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

How to load this class in the correct way?

好的,我有一个 class

class A{
    public D d = new B[=11=]();

    public void foo(){
        B[=11=] b;
        try{
            b = (B[=11=])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[=13=] extends D{
    public static D moveThis(A a){
        throw new Error();
    }

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

我现在新建一个class

class B extends D{
    public D move(Object o){
        return B[=14=].moveThis((A)o);
    }
}

并使用 ByteBuddy 加载它。

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

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

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

(请注意,我正在使用 B[=25=].class.getClassloader() 加载 B。)

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

public Method move:"(Ljava/lang/Object;)LD;"
    stack 1 locals 2
{
        aload_1;
        checkcast   class A;
        invokestatic    Method B[=16=].moveThis:"(LA;)LD;";
        areturn;
}

现在 B 已加载,我重新检测 B[=30=] s.t。它可以处理新的 class.

class B[=17=] extends D{
    public static D moveThis(A a){
        if(a.d instanceof B) throw new RepeatThisMethodException();
        if(a.d instanceof B[=17=]) return new B();
        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[=30=]B[=32=].class.getClassLoader() 重新加载。

既然 B 存在并且可以处理,我让 A 知道它应该从现在开始使用新的 class。

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

    public void foo(){
        B b;
        try{
            b = (B)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
    }
}

并使用相同的 redefineClass 方法重新加载它(因此 AA.class.getClassLoader() 重新加载)。

实际上,A 的新实例将从一开始就使用 B,而现有实例将调用 b.move(this),后者又会调用 B[=41= ].moveThis((A)o)(不能使用 this.

目前这似乎有效。

现在的问题是我们需要更新所有使用 B 版本的 classes 显然,我们不能同时重新加载它们,所以有些会更早一些要迟到了。

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

A 已经重新加载,G 还没有。因此 A(或 A 的任何其他已重新加载的客户端)上的某些方法可能已经触发了 move,而 G 仍在尝试转换为 B[=30=]

没关系。

如果 G 使用 A a 并且未能将 a.d 转换为它期望的版本,它将调用 a.d.move(a),后者又调用 B[=58=].moveThis((A)a)

那样的话,

if(a.d instanceof B) throw new RepeatThisMethodException();

我们在 B[=30=] 中的处理代码确保 G 在其字节码被重新加载并且它知道 B.

之前无法取得进展

或者,如果B可以调用B[=63=].moveThis

相反,我们得到

Exception in thread "MyT1" java.lang.NoClassDefFoundError: A
    at B.move(Unknown Source)

好吧,真不幸。让我们看看是否可以通过将 Object o 的转换移动到 B[=63=].moveThis ...

来避免这个错误
Exception in thread "MyT1" java.lang.NoClassDefFoundError: B[=22=]
    at B.move(Unknown Source)

不,看起来不像。

如何加载 B s.t。它至少可以访问 B[=30=] 并且 B[=30=]A(以及 A 的任何客户端)都可以访问它?

备注

任何一种解决方案都需要支持向上转型。

例如假设我有 D :> B :> C 并且我们使用 B b = new C()(或将 C 的实例传递给期望 B 或 ... 的方法),那么 b.move(b) 必须还是叫 C[=76=].moveThis((C)b).

更新(谢谢,霍尔格)

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

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

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

在任何其他 class 重新加载之前调用 B.move(),实际上足以触发 NoClassDefFoundError.

更新

当我为重新加载 classes 打印 clazz.getClassLoader() 和为新的 classes 打印 b0.getClassLoader() 时,我总是得到相同的 sun.misc.Launcher$AppClassLoader 实例。 =82=]

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