现场描述
在升级了某组件后,应用启动时抛如下异常,导致启动失败:
2017-07-13 15:18:53.388 [C6686525] [] [] ERROR c.y.t.m.a.ServiceAspect - NoSuchMethodError
Method:public com.youzan.api.common.response.PlainResult com.youzan.trade.manage.es.service.impl.SearchServiceImpl.getSearchResult(com.youzan.trade.man
age.api.param.SearchQueryParam)
Arguments:{
}
java.lang.NoSuchMethodError: com.youzan.nsq.client.entity.NSQConfig.setLookupAddresses(Ljava/lang/String;)Lcom/youzan/nsq/client/entity/NSQConfig;
at com.youzan.trade.metrics.NsqReporter.connect(NsqReporter.java:41) ~[trade-metrics-core-1.0-RELEASE.jar:na]
at com.youzan.trade.metrics.NsqReporter.<init>(NsqReporter.java:36) ~[trade-metrics-core-1.0-RELEASE.jar:na]
at com.youzan.trade.metrics.Registry.<init>(Registry.java:54) ~[trade-metrics-core-1.0-RELEASE.jar:na]
过程分析
工程A
引入了组件甲
、乙
以及丙
均依赖公司内二次开发NSQ-client,甲
及乙
为采集应用,均调用了NSQ-Client中NSQCongfig的同名方法,即上日志中提示NoSuchMethodError的方法:
config = new NSQConfig();
config.setLookupAddresses(lookUpAddress);
排查过程中,发现组件甲
依赖的NSQ-Client版本为:2.2.20170424-RELEASE 乙
依赖的NSQ-Client版本为2.3.20170424-RELEASE 粗对比两者的函数签名,方法名,入参都一致。百撕不得其姐。后 组内同学阿奎 debug的时候发现,两个版本的方法返回值不同,当时恍然大悟。自己在问题排查是并没有注意到返回值信息,因为在两个lib中调用时均未获取返回值,但是实际在编译过程中,已经将返回值信息编译到class文件中。
NSQ-Client两个版本相同类中对应方法签名分别为:
#2.3.20170424-RELEASE
public NSQConfig setLookupAddresses(final String lookupAddresses)
#2.2.20170424-RELEASE
public void setLookupAddresses(String lookupAddresses)
导致分别依赖两个版本NSQ-Client且均调用此方法的lib无法共存。
举一反三
public class NSQPublisher{
private Client c = new Client();
public void doSome(){
c.command();
}
public static void main(String[] args){
NSQPublisher publisher = new NSQPublisher();
publisher.doSome();
}
}
public class Client{
public void command(){
System.out.println("Do command");
}
}
定义两个类,编译执行,正常输出:
➜ Desktop javac -cp . Client.java
➜ Desktop javac -cp . NSQPublisher.java
➜ Desktop java NSQPublisher
Do command
此时将Client中的command调整为返回boolean值,再编译执行,问题重现:
➜ Desktop javac -cp . Client.java
➜ Desktop java NSQPublisher
Exception in thread "main" java.lang.NoSuchMethodError: Client.command()V
at NSQPublisher.doSome(NSQPublisher.java:5)
at NSQPublisher.main(NSQPublisher.java:10)
直接查看反编译后信息:
➜ Desktop javap -c NSQPublisher.class
Compiled from "NSQPublisher.java"
public class NSQPublisher {
public NSQPublisher();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class Client
8: dup
9: invokespecial #3 // Method Client."<init>":()V
12: putfield #4 // Field c:LClient;
15: return
public void doSome();
Code:
0: aload_0
1: getfield #4 // Field c:LClient;
4: invokevirtual #5 // Method Client.command:()V
7: return
public static void main(java.lang.String[]);
Code:
0: new #6 // class NSQPublisher
3: dup
4: invokespecial #7 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #8 // Method doSome:()V
12: return
}
昭然若揭。