工作半年遇到最奇葩的问题

简介: 工作半年遇到最奇葩的问题背景公司最近买了一套项目,在启动的时候出现了一系列奇怪的问题,对方的技术栈要求是Tomcat7启动,但是由于我们公司出于安全的考虑所以是要求用Tomcat9进行启动的。问题描述下面情况都是相同war包相同Tomcat情况下系统Tomcat版本能否启动WindowsTomcat7能WindowsTomcat9能macOSTomcat7能macOSTomcat9不能LinuxTomcat7能LinuxTomcat9不能由于对于项目的不熟悉,导致找了很久才找出来原因。

工作半年遇到最奇葩的问题

背景

公司最近买了一套项目,在启动的时候出现了一系列奇怪的问题,对方的技术栈要求是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就可以输出自然顺序,你会发现没有什么规律可言。

解决

到这里上面描述的所有问题我们都能解释通了,接下来就该如何解决了。

  1. 修改Tomcat9的源码,在获取所有Jar包的时候,也对它进行排序
  2. 解决掉有冲突的文件

第一种解决办法只能解决一时问题,即项目能正常启动起来,但是一旦随后涉及到了相关类的修改,那么冲突类的哪个类呢?那么这个问题肯定是一个定时炸弹。

第二种方案是找到有冲突的文件,然后找出不用的那个给删除掉,但是发现删除一个又会蹦出其他的,删除了好几个以后发现由于买的项目代码不规范,所以这种现象特别多,如果单纯靠手工筛选的话极其麻烦。于是就写了一个脚本跑出项目中所有同名类的文件。

脚本思路

  1. 找出所有Java文件
  2. 找到Java文件上package那一行,然后读取此行
  3. package后面的包名与类名拼接存入List集合中
  4. 筛选出集合中相同的内容

具体的脚本代码可以去GitHub中查看。使用简单说明,将想要扫描的项目代码全放在一个文件夹中,例如我要扫描A、B、C、D四个项目。

--/
  --scanDir
   --A
   --B
   --C
   --D

那么我只要引入了Jar包以后如下调用即可

List<String> list = FindDuplicate.findDuplicatePath("/scanDir/");

返回的是一个集合,一条记录表示有一个组冲突文件,两个冲突文件路径被||||||||隔开

脚本代码

往期关于Tomcat文章

相关文章
|
3月前
|
设计模式 算法 安全
2023下半年软件设计师 关于我用了半个月过了软件设计师这件事
这篇文章分享了作者在仅用半个月的业余时间备考并通过2023下半年软件设计师考试的经历,包括备考策略、所用资料和个人建议,同时提供了相关备考资料的获取方式。
|
6月前
|
测试技术 程序员 项目管理
甲方怒喷半小时:一次项目上线失败的深刻教训
小米分享了一次项目上线失败的经历,起因是运营提出一个看似简单的白名单功能。问题包括:没有需求原型导致理解偏差,新成员对项目不熟悉,测试流程不全面,以及人员变动大。解决方案涉及需求确认会、原型图设计、交接制度、团队培训和全流程测试等。这次失败提供了关于需求分析、项目管理及团队协作的教训。
56 2
|
存储 消息中间件 缓存
大环境不好?来看看前同事30K月薪掌握了什么技术!
大环境不好?来看看前同事30K月薪掌握了什么技术!
65 0
|
消息中间件 监控 架构师
公司花50k挖了一个BAT架构师,写代码上线直接内存溢出,被开除了!
公司花50k挖了一个BAT架构师,写代码上线直接内存溢出,被开除了!
|
网络协议 JavaScript 前端开发
面试周连续剧之奇葩遭遇
接着上一篇文章继续聊面试经历,通过前三天的面试经历,梳理总结一下,虽然拿到了三个offer,但是约过的面试还要继续,这是自己的原则,不能随便放别人鸽子,要有面试节操。在分享今天的面试经历的同时,顺便总结一下这几天面试遇到的各种奇葩遭遇,没有别的意思,只是单纯的分享一下,那么马上开启面试周的第四天吧。
122 1
面试周连续剧之奇葩遭遇
|
算法 搜索推荐 数据可视化
花了整整两周,小灰肝出一份算法路线图!
对于我们程序员来说,数据结构和算法是必须要掌握的内功。网络上有很多人整理过编程学习的路线图,但是有关数据结构和算法的却并不多。
226 0
花了整整两周,小灰肝出一份算法路线图!
|
算法 Java
一个多月的时间,终于把这件事做完了!
一个多月的时间,终于把这件事做完了!
120 0
一个多月的时间,终于把这件事做完了!
|
运维 监控 算法
外包干了三年,废了...
先说一下自己的个人情况,大专生,18年通过校招进入湖南某软件公司,干了接近3年的CRUD,
308 0
外包干了三年,废了...
|
Java
Java开发四年,分享我收藏的网站和资源(不看亏一亿)
Java开发四年,分享我收藏的网站和资源(不看亏一亿)
112 0
开会=浪费时间?阿里技术团队这样开项目复盘会
阿里妹导读:复盘是项目结束后必不可少的阶段,好的复盘会议能够有效地促进团队成长。今天,阿里项目管理专家鹿迦以自身的经验,为大家分享如何做好一个项目的复盘。这篇文章分成两个部分,第一部分简单阐述对这种回顾会议的理解,认识会议的真正价值;第二部分是分享个人操作的团队回顾会议流程。
6786 0