Java 仅更改方法结果和类装入器NoSuchMethod
我们从一个jar和一个类以及一个方法开始,如下所示:Java 仅更改方法结果和类装入器NoSuchMethod,java,classloader,name-mangling,Java,Classloader,Name Mangling,我们从一个jar和一个类以及一个方法开始,如下所示: boolean foo( int bar ) { ... } 然而,这个方法的结果是无用的(事实上,总是正确的),并且使用这个结果进行任何操作的客户端都会出现错误。因此,方法更改为: void foo( int bar ) { ... } 以及所有重新编译的项目。因此,我们可以假设此jar的所有用户调用该方法为: foo(14); 没有人使用此表单(如果有人,则超出此问题的范围): 假设没有新的或旧的客户端使用布尔结果。 问题是,在目标
boolean foo( int bar ) { ... }
然而,这个方法的结果是无用的(事实上,总是正确的),并且使用这个结果进行任何操作的客户端都会出现错误。因此,方法更改为:
void foo( int bar ) { ... }
以及所有重新编译的项目。因此,我们可以假设此jar的所有用户调用该方法为:
foo(14);
没有人使用此表单(如果有人,则超出此问题的范围):
假设没有新的或旧的客户端使用布尔结果。
问题是,在目标系统中,加载新jar时,没有升级客户端。未更新的客户端在查找结果为“boolean”的“foo”方法时失败,出现异常“NoSuchMethod”。即:
- 客户机有一个类似“foo(14);”的语句,没有使用方法结果
- 客户机是使用jar with boolean方法编译的
- 客户端和库已加载到目标系统中
- 库更新为使用void方法的新jar
- 客户端使用“NoSuchMethod”崩溃
您可以尝试在实际编译后使用作为修补程序修改类的字节码。在字节码中,这样的方法可以共存。完全可以预料到这种行为。这是因为客户机的编译类包含,其中包括对方法的符号引用 假设我们有class
FooClass
和两种方法:
public boolean foo1(int bar) {
return true;
}
public void foo2(int bar) {
// ...
}
假设我们有另一个类Main
,我们在其中调用两个方法:
FooClass fc = new FooClass();
fc.foo1(1);
fc.foo2(1);
如果我们反汇编Main.class
,我们将看到方法引用不仅在名称上不同,而且在返回的类型上也不同(注意(I)Z和(I)V):
其中常量池包含:
#4 = Methodref #2.#25 // q42340444/FooClass.foo1:(I)Z
#5 = Methodref #2.#26 // q42340444/FooClass.foo2:(I)V
换句话说,它保留了原始方法的返回类型,并在链接库中查找完全相同的方法,这在您的情况下会导致NoSuchMethod
异常
更具体地说,编译后的用户类链接到FooClass.foo:(I)Z
方法,而您的新库不包含这种描述的方法,只包含FooClass.foo:(I)V
结论:如果不保留旧方法签名(最好使用
@弃用的注释注释该方法),或者通过重新编译客户端代码,则无法解决此问题。是的,正如您所描述的,但你的建议是,如果不保留旧方法签名或重新编译客户端代码,就无法解决当前的实际问题。不幸的是,我不能“保留”旧方法和新方法,因为Java不允许两种只在结果类型上不同的方法。请将其重命名为foo2(int-bar)
:)没有其他方法了。对于带有@Override“findClass”的自定义类加载器呢?
7: astore_1
8: aload_1
9: iconst_1
10: invokevirtual #4 // Method q42340444/FooClass.foo1:(I)Z
13: pop
14: aload_1
15: iconst_1
16: invokevirtual #5 // Method q42340444/FooClass.foo2:(I)V
#4 = Methodref #2.#25 // q42340444/FooClass.foo1:(I)Z
#5 = Methodref #2.#26 // q42340444/FooClass.foo2:(I)V