我们从一个包含一个类和一种方法的 jar 开始,例如:
boolean foo( int bar ) { ... }
但是,此方法的结果是无用的(事实上,始终为真),并且使用此结果进行任何操作的客户端都会失败并出现错误。为此,方法改为:
void foo( int bar ) { ... }
并且所有项目都已重新编译。因此,我们可以假设该 jar 的所有用户都调用该方法:
foo(14);
没有人使用该表单(如果有人的话,则超出了此问题的范围):
boolean x = foo(14);
假设没有客户端(无论是新的还是旧的)使用 boolean 结果。
问题在于,在目标系统中,在不升级客户端的情况下加载新 jar。未更新的客户端在查找结果为“boolean”的“foo”方法时失败,并出现异常“NoSuchMethod”。即:
- 客户端有一个类似“foo(14);”的语句不使用方法结果
- 客户端是使用带有 boolean 方法的 jar 来编译的。
- 客户端和库已加载到目标系统中
- 库已使用 void 方法使用新 jar 进行更新
- 客户端因“NoSuchMethod”而崩溃
问题的根源似乎是 Java 和 C/C++ 都不允许两种仅结果不同的方法,但只有 Java“名称修饰”在类加载器(链接器)的名称中包含结果类型在 C/C++ 中)正在寻找。
问题是:在这种情况下,有可能以任何方式欺骗库 jar 或类加载器来跳过“NoSuchMethod”异常吗?
最佳答案
这种行为完全是预料之中的。这是因为客户端编译的类包含 constant pool ,其中包括对方法的符号引用。
假设我们有一个带有两个方法的 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):
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
换句话说,它保留原始方法的返回类型,并在链接库中查找完全相同的方法,这会在您的情况下导致 NoSuchMethod
异常。
更具体地说,编译后的用户类链接到 FooClass.foo:(I)Z
方法,而您的新库不包含具有此类描述的方法,而仅包含 FooClass.foo :(I)V
.
结论:如果不保留旧方法签名(可能最好使用 @Deprecated
注释对该方法进行注释)或重新编译客户端代码,则无法解决此问题.
关于java - 仅更改方法结果和类加载器 NoSuchMethod,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42340444/