内容简介:公司最近买了一套项目,在启动的时候出现了一系列奇怪的问题,对方的技术栈要求是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文章
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning Google Maps API 3
Gabriel Svennerberg / Apress / 2010-07-27 / $39.99
This book is about the next generation of the Google Maps API. It will provide the reader with the skills and knowledge necessary to incorporate Google Maps v3 on web pages in both desktop and mobile ......一起来看看 《Beginning Google Maps API 3》 这本书的介绍吧!