了不得,我可能發現了Jar 包衝突的祕密

三國夢迴發表於2019-06-16

一、前言

這篇是類載入器相關的第三篇:

實戰分析Tomcat的類載入器結構(使用Eclipse MAT驗證)

還是Tomcat,關於類載入器的趣味實驗

 

昨天下午剛寫了篇 類載入器相關的,晚上想著驗證個問題:Tomcat 跑了多個spring web專案,那麼org.springframework.web.servlet.DispatcherServlet 這種類是怎麼個情況呢?多個不同類載入器載入的,同時存在的同名類?

我是打算利用阿里開源的arthas工具來檢視的,但是這個工具只支援 linux。說來也不怕讓人笑話,公司的後端服務,開發環境、測試環境用的windows的,以後交付給客戶不知道是用啥。先不說這個吧,反正我們打的war包,在windows伺服器的tomcat 上沒什麼問題。

但是當我把同樣的war包丟到 linux 上時,發現報錯了,沒啟動成功。。。。hahhah。。。尷尬。。。

錯誤如下:

Caused by: java.lang.NoSuchMethodError: javax.persistence.Table.indexes()[Ljavax/persistence/Index;
        at org.hibernate.cfg.annotations.EntityBinder.processComplementaryTableDefinitions(EntityBinder.java:936)
        at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:824)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3790)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3744)
        at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1410)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1928)
        at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:372)
        at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:454)
        at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:439)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
        ... 38 common frames omitted

 

大概意思是, javax.persistence.Table 的 indexes()方法不存在。

 

二、排查過程

首先,我在idea 中搜了一把 “javax.persistence.Table”,搜到的結果是,hibernate-jpa-2.1-api-1.0.0.Final.jar 這裡面有個同名的類,看了下,indexes()方法是存在的。

好吧,學了一陣子類載入器了,我覺得,首先還是看看,這個類是從哪載入的吧。 懶得去加 -XX:+TraceClassLoading引數了,直接 用阿里的神器,greys(用arthas也可以,arthas是基於greys搞的) 掛載上去,用下面的命令搜尋了一下。

ga?>sc -df javax.persistence.Table    
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                         class-info | javax.persistence.Table                                                          |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        code-source | /home/upload/apache-tomcat-8.5.28/webapps/CAD-WebService/WEB-INF/lib/persistence-api |
|                                                    | -1.0.jar                                                                         |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                               name | javax.persistence.Table                                                          |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        isInterface | true                                                                             |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                       isAnnotation | true                                                                             |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                             isEnum | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                   isAnonymousClass | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                            isArray | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                       isLocalClass | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                      isMemberClass | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        isPrimitive | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        isSynthetic | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        simple-name | Table                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                           modifier | abstract,interface,public                                                        |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                         annotation | java.lang.annotation.Target,java.lang.annotation.Retention                       |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                         interfaces | java.lang.annotation.Annotation                                                  |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        super-class |                                                                                  |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                       class-loader | ParallelWebappClassLoader                                                        |

 

從上圖看出來,javax.persistence.Table 這個類啊,是 webappclassloader 從 webapps/CAD-WebService/WEB-INF/lib/persistence-api -1.0.jar 載入的。

於是我開啟 這個jar包看了下,裡面確實有javax.persistence.Table ,這個類也確實沒有indexes()方法:

 

 看來問題就在這裡,是載入到了錯誤的jar包。 接下來的處理,就要結合業務程式碼,看看到底是從哪引入了這個包,這個包是否需要,不需要的話,直接排除掉即可。(可使用idea 外掛 maven helper)。

 如果只是 儘快解決問題,一般到這步就可以了。但我奇怪的是,windows上為啥沒問題呢???(黑人問號)

 後邊在windows 的 tomcat 啟動指令碼加了 -XX:+TraceClassLoading,發現,該類是從hibernate 那個jar包載入的,所以沒問題。(要讓windows上輸出類載入日誌,要修改點東西。https://www.cnblogs.com/welcomer/p/5068340.html)

 

三、根因分析

我看了下程式碼,這個jar包,確實需要,不能排除掉。。。只是比較奇怪, 在linux上,為啥會優先載入了 persitance-api.jar,難道在windows沒有先載入 persistence-api.jar?

帶著這些疑問,我惡向膽邊生,直接dump了windows下和linux的堆記憶體。

jmap -dump:live,format=b,file=heap3.bin 123072  -----linux的

jmap -dump:live,format=b,file=heap-windows.bin 11640 --windows的

 

eclipse mat 一把開啟 linux的堆dump後,用 oql 語句,查詢了一下所有的 ParallelWebappClassLoader:

 

 

好,再看看 windows 的,操作和上面差不多,直接看結果:

 

 

上圖可見,windows上,是按字母序來的, hibernate那個包,妥妥地排在 persistence-api.jar 的前面。。。 這讓人不得不吐槽下,這個順序怎麼搞的,linux上檔案感覺跟亂序一樣。。。

 

由於 tomcat 8 才有localRepositories 這個欄位,我這裡沒有可執行的原始碼,所以只能大概看看 spring-boot 內嵌的tomcat jar包的原始碼了,大概是這麼個方法:

org.apache.catalina.loader.WebappClassLoaderBase#start

 public void start() throws LifecycleException {

        state = LifecycleState.STARTING_PREP;

        WebResource classes = resources.getResource("/WEB-INF/classes");
        if (classes.isDirectory() && classes.canRead()) {
            localRepositories.add(classes.getURL());
        }
        WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                localRepositories.add(jar.getURL());
                jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

        state = LifecycleState.STARTED;
    }

 

上面標紅處,就是 去 /WEB-INF/lib 下面獲取所有的 jar 包,然後遍歷,加入到localRepositories。 這裡看來,去讀檔案系統後,沒有根據檔名排序吧。。。而正好呢,windows下和linux 下返回的檔案列表,順序不同。

 

四、總結

綜上,可以大概總結下,一般來說,不同作業系統返回的檔案,順序都是不太一致的,如果程式碼裡,直接依賴了這種順序,就會出現這類:

測試:小哥哥,你程式有bug。。。

你:不可能,我這好好的。。。

測試:小哥哥,不騙你,你過來看嘛。。。

你:不看不看,煩不煩??

 

想到以前遇到的一個 spring 迴圈依賴的問題(linux上不行,windows上可以),應該也是這個原因。。。哎。。惱火

 

相關文章