一个类加载的谜团解决了
面對一個好老問題
我在應用程序服務器上遇到一些類加載問題。 這些庫被定義為Maven依賴項,因此被打包到WAR和EAR文件中。 不幸的是,其中一些也已安裝到應用程序服務器中,但版本不同。 啟動應用程序時,我們遇到了與這些類型的問題相關的各種異常。 如果您想深入了解,那么有一篇不錯的IBM文章關于這些異常。
即使我們知道該錯誤是由類路徑上的某些雙重定義的庫引起的,但仍花了兩個多小時來調查我們真正需要的版本以及要刪除的JAR。
同一周在水罐上偶然發生了相同的話題
幾天后,我們參加了“您真的得到了Classloaders嗎? 蘇黎世Java用戶協會會議。 Simon Maple對類加載器進行了非常出色的介紹,并從一開始就介紹了非常深入的細節。 對于許多人來說,這是一次令人大開眼界的會議。 我還必須注意,Simon工作零周轉,他為JRebel進行宣傳。 在這種情況下,輔導課程通常偏向于實際產品,即輔導員的面包。 在這種情況下,我認為西蒙絕對是紳士和道德主義者,保持了適當的平衡。
創建一個工具,解決神秘問題
只是創造另一個
一周后,我花了一些時間來學習程序,而現在我已經有幾個星期沒有時間了,所以我決定創建一個小工具,列出所有在類路徑中的類和JAR文件,以便可以更輕松地查找重復。 我試圖依靠這樣的事實,即類加載器通常是URLClassLoader實例,因此可以調用方法getURLs()來獲取所有目錄名稱和JAR文件。
在這種情況下,單元測試可能非常棘手,因為該功能與類加載器的行為密切相關。 為了務實,我決定只做一些從JUnit開始的手動測試,只要代碼是實驗性的即可。 首先,我想看看這個概念是否值得進一步發展。 我打算執行測試,并查看報告沒有重復類的日志語句,然后執行相同的運行,但是第二次向類路徑添加一些冗余依賴項。 我使用的是JUnit 4.10,在這種情況下,該版本很重要。
我從命令行執行了單元測試,發現沒有重復的類,我感到很高興。 之后,我從Eclipse執行了相同的測試,并且感到驚訝:我冗余定義了21個類!
12:41:51.670 DEBUG c.j.c.ClassCollector - There are 21 redundantly defined classes. 12:41:51.670 DEBUG c.j.c.ClassCollector - Class org/hamcrest/internal/SelfDescribingValue.class is defined 2 times: 12:41:51.671 DEBUG c.j.c.ClassCollector - sun.misc.Launcher$AppClassLoader@7ea987ac:file:/Users/verhasp/.m2/repository/junit/junit/4.10/junit-4.10.jar 12:41:51.671 DEBUG c.j.c.ClassCollector - sun.misc.Launcher$AppClassLoader@7ea987ac:file:/Users/verhasp/.m2/repository/org/hamcrest/hamcrest-core/1.1/hamcrest-core-1.1.jar ...仔細研究一下,我很容易發現JUnit 4.10具有額外的依賴關系,如maven所示。
$ mvn dependency:tree [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building clalotils 1.0.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ clalotils --- [INFO] com.verhas:clalotils:jar:1.0.0-SNAPSHOT [INFO] +- junit:junit:jar:4.10:test [INFO] | \- org.hamcrest:hamcrest-core:jar:1.1:test [INFO] +- org.slf4j:slf4j-api:jar:1.7.7:compile [INFO] \- ch.qos.logback:logback-classic:jar:1.1.2:compile [INFO] \- ch.qos.logback:logback-core:jar:1.1.2:compile [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.642s [INFO] Finished at: Wed Sep 03 12:44:18 CEST 2014 [INFO] Final Memory: 13M/220M [INFO] ------------------------------------------------------------------------這實際上在4.11中已修復,因此如果我將依賴關系更改為JUnit 4.11,則不會遇到此問題。 好。 一半的謎團解決了。 但是,為什么maven命令行執行未報告雙重定義的類?
擴展日志記錄,越來越多的日志記錄我可以發現一條線:
12:46:19.433 DEBUG c.j.c.ClassCollector - Loading from the jar file /Users/verhasp/github/clalotils/target/surefire/surefirebooter235846110768631567.jar這個文件里有什么? 讓我們解壓縮它:
$ ls -l /Users/verhasp/github/clalotils/target/surefire/surefirebooter235846110768631567.jar ls: /Users/verhasp/github/clalotils/target/surefire/surefirebooter235846110768631567.jar: No such file or directory該文件不會退出! 看來maven創建了這個JAR文件,然后在測試執行完成后將其刪除。 再次使用Google搜索,找到了解決方案。
Java從類路徑加載類。 可以在命令行上定義類路徑,但是應用程序類加載器還有其他來源可以從中獲取文件。 一個這樣的源是JAR的清單文件。 JAR文件的清單文件可以定義執行JAR文件中的類所需的其他JAR文件。 Maven創建一個JAR文件,除了清單文件定義清單中列出JAR和列出類路徑的目錄外,該文件不包含其他內容。 這些JAR和目錄不是通過getURLs()方法返回的,因此我的小工具(第一個版本)沒有找到重復項。
出于演示目的,我已經足夠快地在運行mvn test命令的同時復制了文件,并獲得了以下輸出:
$ unzip /Users/verhasp/github/clalotils/target/surefire/surefirebooter5550254534465369201\ copy.jar Archive: /Users/verhasp/github/clalotils/target/surefire/surefirebooter5550254534465369201 copy.jarinflating: META-INF/MANIFEST.MF $ cat META-INF/MANIFEST.MF Manifest-Version: 1.0 Class-Path: file:/Users/verhasp/.m2/repository/org/apache/maven/surefire/surefire-booter/2.8/surefire-booter-2.8.jar file:/Users/verhasp/.m2/repository/org/apache/maven/surefire/surefire-api/2.8/surefire-api-2.8.jar file:/Users/verhasp/github/clalotils/target/test-classes/ file:/Users/verhasp/github/clalotils/target/classes/ file:/Users/verhasp/.m2/repository/junit/junit/4.10/junit-4.10.jar file:/Users/verhasp/.m2/repository/org/hamcrest/hamcrest-core/1.1/hamcrest-core-1.1.jar file:/Users/verhasp/.m2/repository/org/slf4j/slf4j-api/1.7.7/slf4j-api-1.7.7.jar file:/Users/verhasp/.m2/repository/ch/qos/logback/logback-classic/1.1.2/logback-classic-1.1.2.jar file:/Users/verhasp/.m2/repository/ch/qos/logback/logback-core/1.1.2/logback-core-1.1.2.jar Main-Class: org.apache.maven.surefire.booter.ForkedBooter$實際上,除了定義類路徑的清單文件外,別無其他。 但是為什么Maven會這樣做呢? 索納型的人,我個人也認識一些聰明的人。 他們不會無所事事地做這種事情。 創建臨時JAR文件以啟動測試的原因是, 在某些類路徑長度可能超過的操作系統上 ,命令行的長度受到限制 。 即使Java(自Java 6起)本身可以解析類路徑中的通配符 ,也不是maven的選擇。 JAR文件位于Maven存儲庫中的不同目錄中,每個目錄都具有長名稱。 通配符解析不是遞歸的,這是有充分理由的,即使是通配符解析,您也不希望將所有本地存儲都放在類路徑中。
結論
- 不要使用JUnit 4.10! 使用舊的或較新的東西,或為意外做好準備。
- 了解什么是類加載器以及它如何工作,做什么。
- 使用對命令行長度的最大大小有巨大限制的操作系統。
或只是忍受限制。
還有嗎 你的想法?
翻譯自: https://www.javacodegeeks.com/2014/09/a-classloading-mystery-solved.html
總結
以上是生活随笔為你收集整理的一个类加载的谜团解决了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 磷化液配方 磷化液介绍
- 下一篇: JVM PermGen –您在哪里?