例子1
api-1.0
private void setUserId(int userId);api-1.1
private void setUserId(long userId);service-1.0 依赖 api-1.0
void myService(){
api.setUserId(int a);
}
war依赖service-1.0和api-1.1
service.myService();运行阶段
no such method error例子二
jackson-core-2.6.2.jar
public final JsonWriteContext getOutputContext() {return this._writeContext;}jackson-core-2.9.3.jar
public JsonStreamContext getOutputContext() { return _writeContext; }
jackson-dataformat-smile-2.6.2-sources.jar依赖jackson-core-2.6.2.jar
void fun(){
jackson-core#getOutputContext()
}
war依赖jackson-dataformat-smile-2.6.2-sources.jar和 jackson-core-2.9.3.jar
jackson-dataformat-smile-2.6.2-sources.jar#fun()运行阶段
java.lang.NoSuchMethodError: com.fasterxml.jackson.dataformat.smile.SmileGenerator.getOutputContext()Lcom/fasterxml/jackson/core/json/JsonWriteContext;一、为啥明明我只依赖了一个jar包,而且报找不到的方法也在那个包中,还是会报上面那个错误?
这个时候我们要思考一个问题,找不到的那个方法真的在项目当前依赖的包中吗?
这个方法签名必须和JVM运行期间要寻找的那个完全一样,上面两个例子看来都不是完全一样所以才会报这个错误
二、为啥JVM在寻找方法的时候要签名完全一样,不是可以向上转型嘛(方法重载)?
这个问题主要针对例子一,确实有这种情况的方法重载,在main方法中调用入参为int的一个方法,但是类中却只定义了一个入参为long的方法(其他方法签名完全一样)
这个时候是不会报错的,会定位到那个入参为long的方法
But,这个是在编译期执行的
而上面的例子,都是运行期间报的错误
我们所依赖的jar包,其实本来就是.class类型的文件,只有当我们想看细节的时候download的才是.java,也就是说jar包是不参与编译的。
换句话讲,jar包中方法调用(重载)已经完全确定了,所以当执行到对应代码块的时候,找不到对应的方法且无法向上转型
三、总结
这两个例子都是运行期间报错,可能这样看起来觉得很easy,但是实际排查过程特别蛋疼
以第一个例子为例, service-1.0 的myService()方法调用了setUseriId(int args)这个方法,在运行期间JVM去找符合该方法签名的方法,发现没找到所以报错
那么为啥没有找到api-1.1中的setUserId(long userId),我觉着这种向上转型应该是想当然的啊,but并不是这样。在深入理解JVM中有这样说明,静态分派发生在编译阶段,并不是由虚拟机来主导的
我的想当然其实就是一种静态分派(重载方法的确定);而上面这个例子中是已经到了运行期,这个时候方法的确定是很严格的,方法签名必须一致,否则就会报noSuchMethod
这种问题其实不太常见,发生的必要条件是
1)某个低版本jar中的方法签名被高版本所修改
这个例子告诉了我们,打出去的jar包中的方法是修改不得的!!!