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
的一个实例传递给一个需要aB
或…)的方法,那么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();
}