工做半年遇到最奇葩的問題

工做半年遇到最奇葩的問題

背景

公司最近買了一套項目,在啓動的時候出現了一系列奇怪的問題,對方的技術棧要求是Tomcat7啓動,可是因爲咱們公司出於安全的考慮因此是要求用Tomcat9進行啓動的。git

問題描述

下面狀況都是相同war包相同Tomcat狀況下github

系統 Tomcat版本 可否啓動
Windows Tomcat7
Windows Tomcat9
macOS Tomcat7
macOS Tomcat9 不能
Linux Tomcat7
Linux Tomcat9 不能

因爲對於項目的不熟悉,致使找了好久才找出來緣由。查找過程就是用了阿里開源的Arthas 編譯出正在運行時出問題的那個類,發現兩個類來源於不一樣的Jar包,因此問題就轉向了Jar的加載順序是由什麼因素致使了。web

問題深究

兩個同路徑名同類名的類在類加載器只會加載一次安全

出現這個問題的時候查了些資料知道,JVM的類加載是一個樹形的結構,JVM在加載的過程採用的雙親委派的模式,層級越高,那麼類加載器會越早的加載其路徑下的類。下面是Tomcat的類加載器所在的級別。架構

Bootstrap
          |
       System
          |
       Common
       /     \
  Webapp1   Webapp2 .

咱們能夠知道出問題的兩個Jar是在相同的類加載器中,因此排除了不一樣級別類加載器致使的問題。app

Tomcat7加載Jar包原理

Tomcat本身實現了本身的類加載器,用於加載本身本地項目中jar包中的全部class文件,因此在相同的類加載器下,若是有相同路徑名和類名那麼加載順序就是根據jar包的順序來決定的。誰的jar包先進來,那麼就先加載哪一個類。webapp

可是爲何在Tomcat7全部環境都能運行正常,而在Tomcat9中就不行了呢?因而就查看了Tomcat7的源碼在Context加載項目中的jar包時post

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文章

相關文章
相關標籤/搜索