背景
从ECS在迁移到容器之后,迁移了一下代码部署的位置,以及Tomcat 从 7 升级到 8,业务开始频繁出现NoSuchMethodError。
Tomcat7和Tomcat8加载的区别和相同点
Tomcat 8.0 :class loader how to work in tomcat8
Tomcat 7.0 :class load how to work in tomcat7
结论
加载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的读取顺序有关.
其他链接
https://bz.apache.org/bugzilla/show_bug.cgi?id=57129
总结
世界是科学的