一次特殊的jar包冲突

1,671 阅读2分钟

例子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包中的方法是修改不得的!!!