如何以正确的方式加载此 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
方法重新加载它(因此 A
由 A.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 中所讨论的,这在实际代码生成中是一个问题。
好的,我有一个 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
方法重新加载它(因此 A
由 A.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 中所讨论的,这在实际代码生成中是一个问题。