背景
业务从ECS在迁移到容器之后,Tomcat从7升级到了8.5,开始频繁出现NoSuchMethodError. 并向我反馈以前不会。期待我可以给出一个结论
在查阅资料和研究其源代码之后,我发现了如下区别,并稳定的复现了 jar包冲突
的情况下 必然出现
该错误和 必然不出现
该错误。
Tomcat7和Tomcat8加载的区别和相同点
Tomcat7和8 Classloader说明文档
- Tomcat 8.0 :class loader how to work in tomcat8
- Tomcat 7.0 :class load how to work in tomcat7
先说结论
加载class的方式都是一样的,都是读取classpath下的jar包的class文件数据,但由于lib的排序方式发生了变化,导致读取class发生了不一致. 从而引发NoSuchMethodError。
相同点
1、读取class的方式,伪代码如下:
//jarFiles ===> WEB-INF/lib 下的lib文件
for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
jarEntry = jarFiles[i].getJarEntry(jarEntryPath);
if (jarEntry != null) {
//tomcat biz
//返回 ResourceEntry
}
}
谁先找到class文件谁先加载,问题就出现在jarFiles的排列顺序 jarFiles的是直接读取的WEB-INF/lib下的文件,那么读取的顺序就决定了加载的顺序
Tomcat7 读取顺序代码如下: FileDirContext.java (在Tomcat8中被重构掉)
protected List<NamingEntry> list(File file) {
String[] names = file.list();
Arrays.sort(names);//这里进行了一次排序,得出的结果和我们在WEB-INF/lib下执行ll命令一致
//剩下的省略掉
}
Tomcat8 读取顺序代码如下:DirResourceSet.java
@Override
public String[] list(String path) {
String webAppMount = getWebAppMount();
if (path.startsWith(webAppMount)) {
File f = file(path.substring(webAppMount.length()), true);
if (f == null) {
return EMPTY_STRING_ARRAY;
}
//读取文件,然而没有了排序. 完全无序,具体的读取全看native的实现,这里我不清楚
String[] result = f.list();
if (result == null) {
return EMPTY_STRING_ARRAY;
} else {
return result;
}
} else {
//省略部分
return EMPTY_STRING_ARRAY;
}
}
既然没有了顺序,那么我们就不知道他会从哪一个jar中读取,但是还是有个规律的,如果n次发布的jar包都没有变化,那么n从读取的顺序也是没有变化的.
从上述结论定制NoSuchMethodError
1、打包jar包3份,分别是
-rw-r--r-- 1 admin wheel 46K 2 16 08:19 safe-common-0.1.0-SNAPSHOT.jar 1
-rw-r--r-- 1 admin wheel 46K 2 16 08:08 safe-common-1.0-SNAPSHOT.jar 2
-rw-r--r-- 1 admin wheel 46K 2 15 21:37 safe-common-2.0-SNAPSHOT.jar 3
1和3 代码一致,比2多出一个方法.
public class NoSuchMethodTest {
public NoSuchMethodTest() {
}
//jar包1,2,3中都有
public static void handle(String test) {
System.out.println("handle===>" + test);
}
//jar包1,3中才有
public static void handle(String test, String test2) {
System.out.println("2.0===>handle===>" + test + ":" + test2);
}
}
调用代码
@RequestMapping("zz")
@ResponseBody
public String xx() {
try {
NoSuchMethodTest.handle("123", "123");
} catch (Throwable e) {
return e.getMessage();
}
return "zz";
}
测试1
将2和3直接放到WEB-INF/lib下,调用
调用结果如下,必然发生NoSuchMethodError
➜ lib curl 127.0.0.1:18080/zz.json com.raycloud.safe.group.api.such.NoSuchMethodTest.handle(Ljava/lang/String;Ljava/lang/String;)V%
测试2
将1、2、3号jar包都放入,因为在tomcat7下排列顺序为为1、2、3号jar包依次排列,所以没有问题
在而Tomcat8下则跟file.list的读取顺序有关.
其他链接
总结
世界是科学的
今天的文章Tomcat NoSuchMethodError到底是怎么发生的分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16046.html