内容简介:公司最近买了一套项目,在启动的时候出现了一系列奇怪的问题,对方的技术栈要求是Tomcat7启动,但是由于我们公司出于安全的考虑所以是要求用Tomcat9进行启动的。下面情况都是相同war包相同Tomcat情况下由于对于项目的不熟悉,导致找了很久才找出来原因。查找过程就是用了阿里开源的
工作半年遇到最奇葩的问题
背景
公司最近买了一套项目,在启动的时候出现了一系列奇怪的问题,对方的技术栈要求是Tomcat7启动,但是由于我们公司出于安全的考虑所以是要求用Tomcat9进行启动的。
问题描述
下面情况都是相同war包相同Tomcat情况下
系统 | Tomcat版本 | 能否启动 |
---|---|---|
Windows | Tomcat7 | 能 |
Windows | Tomcat9 | 能 |
macOS | Tomcat7 | 能 |
macOS | Tomcat9 | 不能 |
Linux | Tomcat7 | 能 |
Linux | Tomcat9 | 不能 |
由于对于项目的不熟悉,导致找了很久才找出来原因。查找过程就是用了阿里开源的 Arthas
编译出正在运行时出问题的那个类, 发现两个类来源于不同的Jar包,所以问题就转向了Jar的加载顺序是由什么因素导致了。
问题深究
两个同路径名同类名的类在类加载器只会加载一次
出现这个问题的时候查了些资料知道,JVM的类加载是一个树形的结构,JVM在加载的过程采用的双亲委派的模式,层级越高,那么类加载器会越早的加载其路径下的类。下面是Tomcat的类加载器所在的级别。
Bootstrap | System | Common / \ Webapp1 Webapp2 .
我们可以知道出问题的两个Jar是在相同的类加载器中,所以排除了不同级别类加载器导致的问题。
Tomcat7加载Jar包原理
Tomcat自己实现了自己的类加载器,用于加载自己本地项目中jar包中的所有class文件,所以在相同的类加载器下,如果有相同路径名和类名那么加载顺序就是根据jar包的顺序来决定的。谁的jar包先进来,那么就先加载哪个类。
但是为什么在Tomcat7所有环境都能运行正常,而在Tomcat9中就不行了呢?于是就查看了Tomcat7的源码在Context加载项目中的jar包时
Tomcat7加载jar部分,在 WebappLoader.setRepositories()
方法中,粘贴出其中重要代码。
// Looking up directory /WEB-INF/lib in the context NamingEnumeration<NameClassPair> enumeration = null; try { //这一句是获得jar包的路径 enumeration = libDir.list(""); } catch (NamingException e) { IOException ioe = new IOException(sm.getString( "webappLoader.namingFailure", libPath)); ioe.initCause(e); throw ioe; }
list是获得了应用中WEB-INF下lib下所有jar包的路径。我们跟踪进去发现 FileDirContext
的list方法中有下面这一句
Arrays.sort(names); // Sort alphabetically
我们可以发现在Tomcat7中对获得所有jar包作了一个 排序 的动作。对jar包进行了首字母a-z进行了排序。而我们所期望加载的那个jar包首字母正好在错误jar包的前面。
Tomcat9加载Jar包原理
上面我们知道了为什么在所有项目中Tomcat7能启动起来的原因了,是因为Tomcat7做了排序的动作,那么在Tomcat9加载Jar包时,又是怎么做的呢?
Tomcat9在加载源码的时候是通过 StandardRoot.processWebInfLib()
方法进行加载的
protected void processWebInfLib() throws LifecycleException { WebResource[] possibleJars = listResources("/WEB-INF/lib", false); for (WebResource possibleJar : possibleJars) { if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) { createWebResourceSet(ResourceSetType.CLASSES_JAR, "/WEB-INF/classes", possibleJar.getURL(), "/"); } } }
在这我们可以看到Tomcat没有对取出来的Jar作任何动作,仅仅是 File file = new File()
这样遍历出来了。那么为什么相同的Tomcat9相同的War包在Windos能启动起来,但是在macOS和 Linux 中都启动不起来呢?经过试验发现 Java 的获取文件夹下面的所有文件是跟操作系统的文件系统有关系的,相同的文件夹内容,在Windows中取出来,输出名字你会发现输出是经过a-z排序过的,但是在macOS或者Linux中你可以根据命令 ll -fi
就可以输出自然顺序,你会发现没有什么规律可言。
解决
到这里上面描述的所有问题我们都能解释通了,接下来就该如何解决了。
- 修改Tomcat9的源码,在获取所有Jar包的时候,也对它进行排序
- 解决掉有冲突的文件
第一种解决办法只能解决一时问题,即项目能正常启动起来,但是一旦随后涉及到了相关类的修改,那么冲突类的哪个类呢?那么这个问题肯定是一个定时炸弹。
第二种方案是找到有冲突的文件,然后找出不用的那个给删除掉,但是发现删除一个又会蹦出其他的,删除了好几个以后发现由于买的项目代码不规范,所以这种现象特别多,如果单纯靠手工筛选的话极其麻烦。于是就写了一个脚本跑出项目中所有同名类的文件。
脚本思路
package package
具体的脚本代码可以去 GitHub 中查看。使用简单说明,将想要扫描的项目代码全放在一个文件夹中,例如我要扫描A、B、C、D四个项目。
--/ --scanDir --A --B --C --D
那么我只要引入了Jar包以后如下调用即可
List<String> list = FindDuplicate.findDuplicatePath("/scanDir/");
返回的是一个集合,一条记录表示有一个组冲突文件,两个冲突文件路径被 ||||||||
隔开
脚本代码
往期关于Tomcat文章
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Shell 编程里面的奇葩字符
- (授权转载)10段奇葩的代码注释
- 记一次面试腾讯的奇葩经历
- 堪称奇葩的BTC诈骗案例分析
- 借贷宝WWDC办招 聘 奇葩老外面试回复走红
- 一键生成果照,程序员又出奇葩软件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。