日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Jar Hell变得轻松–用jHades揭秘classpath

發(fā)布時(shí)間:2023/12/3 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Jar Hell变得轻松–用jHades揭秘classpath 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Java開發(fā)人員將不得不面對(duì)的最困難的問題是類路徑錯(cuò)誤: ClassNotFoundException , NoClassDefFoundError ,Jar Hell, Xerces Hell和company。

在本文中,我們將探究這些問題的根本原因,并了解最小的工具( JHades )如何幫助快速解決這些問題。 我們將看到為什么Maven無法(始終)防止類路徑重復(fù),并且:



  • 處理地獄的唯一方法
  • 裝載機(jī)
  • 類加載器鏈
  • 類加載器的優(yōu)先級(jí):父優(yōu)先與父末
  • 調(diào)試服務(wù)器啟動(dòng)問題
  • 用jHades理解Jar Hell
  • 避免類路徑問題的簡(jiǎn)單策略
  • 類路徑在Java 9中得到修復(fù)嗎?

處理地獄的唯一方法

類路徑問題的調(diào)試可能很耗時(shí),并且往往在最壞的時(shí)間和地點(diǎn)發(fā)生:發(fā)布之前,通常在開發(fā)團(tuán)隊(duì)幾乎沒有訪問權(quán)限的環(huán)境中。

它們也可能發(fā)生在IDE級(jí)別,并成為生產(chǎn)力降低的根源。 我們的開發(fā)人員往往會(huì)及早發(fā)現(xiàn)這些問題,這是通常的回答:

讓我們嘗試為我們節(jié)省一些時(shí)間,并深入探討這一點(diǎn)。 這些類型的問題很難通過反復(fù)試驗(yàn)來解決。 解決這些問題的唯一真正方法是真正了解正在發(fā)生的事情 ,但是從哪里開始呢?

事實(shí)證明,Jar Hell問題比其看起來要簡(jiǎn)單,并且僅需幾個(gè)概念即可解決它們。 最后,造成Jar Hell問題的常見根本原因是:

  • 一個(gè)罐子不見了
  • 一個(gè)罐子太多了
  • 一個(gè)班級(jí)在什么地方不可見

但是,如果這么簡(jiǎn)單,那么為什么類路徑問題很難調(diào)試?

Jar Hell堆棧跟蹤不完整

原因之一是類路徑問題的堆棧跟蹤缺少許多信息來解決問題。 以以下堆棧跟蹤為例:

java.lang.IncompatibleClassChangeError: Class org.jhades.SomeServiceImpl does not implement the requested interface org.jhades.SomeService org.jhades.TestServlet.doGet(TestServlet.java:19)

它說一個(gè)類沒有實(shí)現(xiàn)某個(gè)接口。 但是,如果我們查看類源代碼:

public class SomeServiceImpl implements SomeService { @Overridepublic void doSomething() {System.out.println( "Call successful!" );} }

好了,該類顯然實(shí)現(xiàn)了缺少的接口! 那么發(fā)生了什么呢? 問題在于堆棧跟蹤缺少很多信息 ,這些信息對(duì)于理解該問題至關(guān)重要。

堆棧跟蹤可能應(yīng)該包含這樣的錯(cuò)誤消息(我們將了解這是什么意思):

類SomeServiceImpl類加載器/路徑/到/ Tomcat的/ lib中不實(shí)現(xiàn)接口SomeService從類加載器加載的Tomcat - Web應(yīng)用程序- /路徑/到/ Tomcat的/ web應(yīng)用/測(cè)試

這至少是從哪里開始的指示:

  • 剛學(xué)習(xí)Java的人至少會(huì)知道,對(duì)于了解正在發(fā)生的事情,必不可少的是類加載器這一概念。
  • 很明顯,涉及的一個(gè)類不是從WAR加載的,而是從服務(wù)器的某個(gè)目錄( SomeServiceImpl )加載的。

什么是類加載器?

首先,類加載器只是Java類,更確切地說是運(yùn)行時(shí)類的實(shí)例。 它不是 JVM不可訪問的內(nèi)部組件,例如垃圾收集器。

以Tomcat的WebAppClassLoader為例,這里是javadoc 。 如您所見,它只是一個(gè)普通的Java類,如果需要,我們甚至可以編寫我們自己的類加載器。

ClassLoader都可以用作類加載器。 類加載器的主要職責(zé)是知道類文件的位置,然后根據(jù)JVM的要求加載類。

一切都鏈接到類加載器

JVM中的每個(gè)對(duì)象都通過getClass()鏈接到其類,而每個(gè)類都通過getClassLoader()鏈接到類加載器。 這意味著:

JVM中的每個(gè)對(duì)象都鏈接到一個(gè)類加載器!

讓我們看看如何使用此事實(shí)對(duì)類路徑錯(cuò)誤方案進(jìn)行故障排除。

如何查找類文件的實(shí)際位置

我們來看一個(gè)對(duì)象,看看它的類文件在文件系統(tǒng)中的位置:

System.out.println(service.getClass() .getClassLoader().getResource("org/jhades/SomeServiceImpl.class"));

這是類文件的完整路徑: jar:file:/Users/user1/.m2/repository/org/jhades/jar-2/1.0-SNAPSHOT/jar-2-1.0-SNAPSHOT.jar!/org/jhades/SomeServiceImpl.class
jar:file:/Users/user1/.m2/repository/org/jhades/jar-2/1.0-SNAPSHOT/jar-2-1.0-SNAPSHOT.jar!/org/jhades/SomeServiceImpl.class

如我們所見,類加載器只是一個(gè)運(yùn)行時(shí)組件,它知道文件系統(tǒng)中查找類文件的位置以及如何加載它們。

但是,如果類加載器找不到給定的類,會(huì)發(fā)生什么?

類加載器鏈

缺省情況下,在JVM中,如果類加載器找不到類,則它將要求其父類加載器提供相同的類,依此類推。

這一直持續(xù)到JVM引導(dǎo)類加載器為止(稍后會(huì)對(duì)此進(jìn)行更多介紹)。 這個(gè)類加載器鏈?zhǔn)穷惣虞d器委托鏈 。

類加載器的優(yōu)先級(jí):父優(yōu)先與父末

一些類加載器將請(qǐng)求立即委派給父類加載器,而無需先在其自己的已知目錄集中搜索類文件。 據(jù)說在此模式下運(yùn)行的類加載器處于“ 父優(yōu)先”模式。

如果類加載器首先在本地查找類,并且僅在查詢父類(如果找不到該類)之后才查找,則該類加載器被稱為在“上一父代”模式下工作。

所有應(yīng)用程序都有類加載器鏈嗎?

甚至最簡(jiǎn)單的Hello World主方法也具有3個(gè)類加載器:

  • 應(yīng)用程序類加載器,負(fù)責(zé)加載應(yīng)用程序類(父級(jí)優(yōu)先)
  • 擴(kuò)展類加載器,它從$JAVA_HOME/jre/lib/ext (先是父級(jí))加載jar
  • Bootstrap類加載器,用于加載JDK附帶的任何類,例如java.lang.String (無父類加載器)

WAR應(yīng)用程序的類加載器鏈?zhǔn)鞘裁礃拥?#xff1f;

對(duì)于Tomcat或Websphere之類的應(yīng)用程序服務(wù)器,類加載器鏈的配置與簡(jiǎn)單的Hello World主方法程序不同。 以Tomcat類加載器鏈為例:

在這里,我們希望每個(gè)WAR都在WebAppClassLoader運(yùn)行,該WebAppClassLoader以父級(jí)末尾模式工作(也可以將其設(shè)置為父級(jí)末尾)。 通用類加載器加載在服務(wù)器級(jí)別安裝的庫(kù)。

Servlet規(guī)范對(duì)類加載有何看法?

Servlet容器規(guī)范僅定義了類加載器鏈行為的一小部分:

  • WAR應(yīng)用程序在其自己的應(yīng)用程序類加載器上運(yùn)行,可以與其他應(yīng)用程序共享或不與其他應(yīng)用程序共享
  • WEB-INF/classes的文件優(yōu)先于其他所有文件

在那之后,任何人都可以猜測(cè)! 其余的完全開放給容器提供商解釋。

為什么在供應(yīng)商之間沒有通用的類加載方法?

通常,默認(rèn)情況下,通常將諸如Tomcat或Jetty之類的開源容器配置為先在WAR中查找類,然后才在服務(wù)器類加載器中搜索。

這使應(yīng)用程序可以使用自己的庫(kù)版本來覆蓋服務(wù)器上可用的庫(kù)。

大型鐵服務(wù)器呢?

諸如Websphere之類的商業(yè)產(chǎn)品將嘗試“出售”自己的服務(wù)器提供的庫(kù),這些庫(kù)默認(rèn)情況下優(yōu)先于WAR上安裝的庫(kù)。

假設(shè)您購(gòu)買了該服務(wù)器,并且還希望使用它提供的JEE庫(kù)和版本,則通常會(huì)這樣做。

這給部署到某些商業(yè)產(chǎn)品帶來了極大的麻煩,因?yàn)樗鼈兊男袨榉绞讲煌陂_發(fā)人員用來在其工作站中運(yùn)行應(yīng)用程序的Tomcat或Jetty。 我們將在此解決方案上看到更多。

常見問題:重復(fù)的類版本

目前,您可能有一個(gè)很大的問題:

如果WAR中有兩個(gè)罐子包含完全相同的類怎么辦?

答案是行為是不確定的, 只有在運(yùn)行時(shí)才會(huì)選擇兩個(gè)類之一 。 選擇哪一個(gè)取決于類加載器的內(nèi)部實(shí)現(xiàn),無法預(yù)先知道。

但是幸運(yùn)的是,如今大多數(shù)項(xiàng)目都使用Maven,Maven通過確保僅將給定jar的一個(gè)版本添加到WAR中來解決此問題。

因此,Maven項(xiàng)目可以不受這種特定類型的Jar Hell的影響,對(duì)嗎?

為什么Maven不能防止類路徑重復(fù)

不幸的是,Maven無法在所有Jar Hell情況下提供幫助。 實(shí)際上,許多不使用某些質(zhì)量控制插件的Maven項(xiàng)目在類路徑上都可以有數(shù)百個(gè)重復(fù)的類文件(我看到中繼有500多個(gè)重復(fù)項(xiàng))。 這有幾個(gè)原因:

  • 圖書館出版商有時(shí)會(huì)更改罐子的工件名稱:發(fā)生這種情況是由于品牌重塑或其他原因。 以JAXB jar為例。 Maven不可能將這些工件識(shí)別為同一罐子!
  • 某些jar具有或不具有依賴關(guān)系而發(fā)布:一些庫(kù)提供程序提供jar的“具有依賴關(guān)系”版本,其中包括其他jar。 如果兩個(gè)版本都具有傳遞依賴,則最終將導(dǎo)致重復(fù)。
  • 有些類在jar之間復(fù)制:有些庫(kù)創(chuàng)建者在遇到某個(gè)類的需要時(shí),只會(huì)從另一個(gè)項(xiàng)目中獲取它,然后將其復(fù)制到新的jar中而不更改包名。

所有的班級(jí)文件重復(fù)都是危險(xiǎn)的嗎?

如果重復(fù)的類文件存在于同一個(gè)類加載器中,并且兩個(gè)重復(fù)的類文件完全相同,那么首先選擇哪個(gè)是無關(guān)緊要的–這種情況并不危險(xiǎn)。

如果兩個(gè)類文件都在同一個(gè)類加載器中,并且它們不相同,則無法在運(yùn)行時(shí)選擇一個(gè),這是有問題的,并且在部署到不同環(huán)境時(shí)會(huì)表現(xiàn)出來。

如果類文件位于兩個(gè)不同的類加載器中,則永遠(yuǎn)不會(huì)將它們視為相同(請(qǐng)參見后面的類標(biāo)識(shí)危機(jī)部分)。

如何避免WAR類路徑重復(fù)?

例如,可以通過使用Maven Enforcer插件來避免此問題,并啟用“ 禁止重復(fù)類”的額外規(guī)則。

您也可以使用JHades WAR重復(fù)類報(bào)告快速檢查您的WAR是否干凈。 該工具可以過濾“無害”重復(fù)項(xiàng)(相同的類文件大小)。

但是,即使是干凈的WAR也會(huì)存在部署問題:類丟失,從服務(wù)器而不是WAR中獲取的類以及版本錯(cuò)誤的類,類強(qiáng)制轉(zhuǎn)換異常等。

使用JHades調(diào)試類路徑

類路徑問題通常在應(yīng)用服務(wù)器啟動(dòng)時(shí)出現(xiàn),這是一個(gè)特別糟糕的時(shí)刻,尤其是在部署到訪問受限的環(huán)境中時(shí)。

JHades是幫助處理Jar Hell的工具(免責(zé)聲明:我寫的)。 它是一個(gè)單一的Jar,除了JDK7本身之外,沒有任何依賴性。 這是一個(gè)如何使用它的示例:

new JHades().printClassLoaders().printClasspath().overlappingJarsReport().multipleClassVersionsReport().findClassByName("org.jhades.SomeServiceImpl")

這會(huì)將類加載器鏈,罐子,重復(fù)類等打印到屏幕上。

調(diào)試服務(wù)器啟動(dòng)問題

在服務(wù)器無法正常啟動(dòng)的情況下,JHades可以很好地工作。 提供了一個(gè)servlet偵聽器,即使在應(yīng)用程序的任何其他組件開始運(yùn)行之前,該偵聽器也可以打印類路徑調(diào)試信息。

ClassCastException和類身份危機(jī)

對(duì)Jar Hell進(jìn)行故障排除時(shí),請(qǐng)注意ClassCastExceptions 。 在JVM中,不僅通過完全限定的類名來標(biāo)識(shí)類,而且通過其類加載器來標(biāo)識(shí)該類。

這是違反直覺的,但事后看來是有道理的:我們可以使用相同的包和名稱創(chuàng)建兩個(gè)不同的類,將它們放入兩個(gè)jar中,然后放入兩個(gè)不同的類加載器中。 可以說一個(gè)擴(kuò)展了ArrayList ,另一個(gè)是Map 。

因此,這些類是完全不同的(盡管名稱相同),并且不能相互轉(zhuǎn)換! 運(yùn)行時(shí)將拋出CCE以防止發(fā)生這種潛在的錯(cuò)誤情況,因?yàn)闊o法保證這些類是可強(qiáng)制轉(zhuǎn)換的。

將類加載器添加到類標(biāo)識(shí)符是Java早期發(fā)生的類身份危機(jī)的結(jié)果。

避免類路徑問題的策略

說起來容易做起來難,但是避免與類路徑相關(guān)的部署問題的最佳方法是在“上一步”模式下運(yùn)行生產(chǎn)服務(wù)器。

這樣,WAR的類版本優(yōu)先于服務(wù)器上的類版本,并且在生產(chǎn)環(huán)境和開發(fā)人員工作站中使用了相同的類,這些工作站可能正在使用Tomcat,Jetty或其他開源的Parent Last服務(wù)器。

在某些服務(wù)器(例如Websphere)中,這還不夠,并且您還必須在清單文件中提供特殊屬性以顯式關(guān)閉某些庫(kù),例如JAX-WS。

修復(fù)Java 9中的類路徑

在Java 9中,類路徑已完全通過新的Jigsaw模塊化系統(tǒng)進(jìn)行了改進(jìn)。 在Java 9中,可以將jar聲明為模塊,它將在其自己的隔離類加載器中運(yùn)行,該類加載器以O(shè)SGI方式從其他類似的模塊類加載器讀取類文件。

如果需要,這將允許同一版本的Jar的多個(gè)版本共存。

結(jié)論

最后,Jar Hell問題并不是像最初看起來那樣低級(jí)或難以解決。 都是關(guān)于zip文件(jar)在某些目錄中存在/不存在,如何查找這些目錄以及如何在訪問受限的環(huán)境中調(diào)試類路徑。

通過了解一組有限的概念(例如類加載器,類加載器鏈和父級(jí)/父級(jí)后代模式),可以有效地解決這些問題。

外部鏈接

這份演講“您真的從ZeroTurnaround的Jevgeni Kabanov( JRebel公司) 獲得類加載器”是有關(guān)Jar Hell以及與類路徑相關(guān)的不同類型異常的重要資源。

翻譯自: https://www.javacodegeeks.com/2014/10/jar-hell-made-easy-demystifying-the-classpath-with-jhades.html

總結(jié)

以上是生活随笔為你收集整理的Jar Hell变得轻松–用jHades揭秘classpath的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。