背景
从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
@Overridepublic 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")@ResponseBodypublic 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
总结
世界是科学的
