<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Jar 包依賴沖突排查思路和解決方法

          共 12867字,需瀏覽 26分鐘

           ·

          2021-01-05 02:06

          作者:jingQ

          https://www.sevenyuan.cn/

          起因

          應(yīng)用從 jdk7 升級到 jdk8,終于可以用上新特性的語法進(jìn)行代碼編寫,通過幾輪開發(fā)、測試和驗證后,在上預(yù)發(fā)環(huán)境時,應(yīng)用突然無法啟動,查看 tomcat 報錯原因,發(fā)現(xiàn)是?類轉(zhuǎn)換失敗?ClassCastException

          報錯原因

          Class path contains multiple SLF4J binding

          23-May-2019?16:04:25.300?INFO?[localhost-startStop-1]?org.apache.jasper.servlet.TldScanner.scanJars?At?least?one?JAR?was?scanned?for?TLDs?yet?contained?no?TLDs.?Enable?debug?logging?for?this?logger?for?a?complete?list?of?JARs?that?were?scanned?but?no?TLDs?were?found?in?them.?Skipping?unneeded?JARs?during?scanning?can?improve?startup?time?and?JSP?compilation?time.
          SLF4J:?Class?path?contains?multiple?SLF4J?bindings.
          SLF4J:?Found?binding?in?[jar:file:/home/admin/xxx/WEB-INF/lib/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
          SLF4J:?Found?binding?in?[jar:file:/home/admin/xxx/WEB-INF/lib/logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
          SLF4J:?See?http://www.slf4j.org/codes.html#multiple_bindings?for?an?explanation.
          SLF4J:?Actual?binding?is?of?type?[org.slf4j.impl.Log4jLoggerFactory]

          報錯原因

          org.slf4j.impl.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext

          org.springframework.beans.factory.BeanCreationException:?Error?creating?bean?with?name?'cn.com.xxx.framework.log.integration.LogbackInitializer#0'?defined?in?class?path?resource?[spring/spring-log-init.xml]:?Invocation?of?init?method?failed;?nested?exception?is?java.lang.ClassCastException:?org.slf4j.impl.Log4jLoggerFactory?cannot?be?cast?to?ch.qos.logback.classic.LoggerContext
          ????...????
          Caused?by:?java.lang.ClassCastException:?org.slf4j.impl.Log4jLoggerFactory?cannot?be?cast?to?ch.qos.logback.classic.LoggerContext
          ????#?出問題的加載地方
          ?at?ch.qos.logback.ext.spring.LogbackConfigurer.initLogging(LogbackConfigurer.java:72)
          ?at?cn.com.xxx.framework.log.integration.LogbackInitializer.init(LogbackInitializer.java:49)
          ?at?sun.reflect.NativeMethodAccessorImpl.invoke0(Native?Method)
          ?at?sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          ?at?sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          ?at?java.lang.reflect.Method.invoke(Method.java:498)
          ?at?org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1706)
          ?at?org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1645)
          ?at?org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
          ?...?26?more
          23-May-2019?15:59:12.398?SEVERE?[localhost-startStop-1]?org.apache.catalina.core.StandardContext.startInternal?One?or?more?listeners?failed?to?start.?Full?details?will?be?found?in?the?appropriate?container?log?file

          查看報錯代碼

          public?static?void?initLogging(String?location)?throws?FileNotFoundException,?JoranException?{
          ???String?resolvedLocation?=?SystemPropertyUtils.resolvePlaceholders(location);
          ???URL?url?=?ResourceUtils.getURL(resolvedLocation);
          ???LoggerContext?loggerContext?=?(LoggerContext)StaticLoggerBinder.getSingleton().getLoggerFactory();
          ???loggerContext.reset();
          ???new?ContextInitializer(loggerContext).configureByResource(url);
          }

          可以看到,通過?StaticLoggerBinder.getSingleton().getLoggerFactory()?獲取 logger 上下文這段代碼報錯了,通過仔細(xì)定位,發(fā)現(xiàn)了有兩個?StaticLoggerBinder?類

          更重要的是,他們兩兄弟竟然雖然不是同一個 jar 包,但是包路徑和名稱都一模一樣?。?!

          由于我們需要的是?logback?包,而不是?slf4j-log4j12?包,所以需要排除掉?slf4j-log4j12?依賴。

          詳細(xì):Maven從入門到放棄


          解決方法

          ① 通過 POM 文件排查包沖突

          ② 安裝 IDEA 的插件?Maven Helper

          ③ 定位到編譯 WAR 包的 POM 文件(我們框架定義的在 Deploy 模塊中)

          ④ 在搜索框中,輸入搜索內(nèi)容,點(diǎn)擊右鍵可以看到選項框

          • Jump To Source(跳轉(zhuǎn)到源文件處)

          • Exclude(排除掉)

          例如我點(diǎn)擊了?Exclude?,就能看到 pom 文件中,這個依賴就被排除掉了

          <dependency>
          ????<groupId>cn.com.xxxgroupId>
          ????<artifactId>framework-conf-clientartifactId>
          ????<version>${xqy.framework.version}version>
          ????<exclusions>
          ????????<exclusion>
          ????????????<artifactId>slf4j-log4j12artifactId>
          ????????????<groupId>org.slf4jgroupId>
          ????????exclusion>
          ????exclusions>
          dependency>

          排除依賴后,提交代碼,重新打包,部署一條龍,順利啟動~


          思考

          包沖突解決是簡單的,通過 maven 插件可以精確找到依賴,然后進(jìn)行 Exclude,可是在本地開發(fā)、測試環(huán)境都沒有出現(xiàn)的問題,卻在預(yù)發(fā)環(huán)境出現(xiàn)了,所以排除了業(yè)務(wù)邏輯代碼的原因,簡單考慮了幾個因素和原因:

          • jdk 版本

          • tomcat 版本

          • 類加載機(jī)制

          • 第三方 jar 互相依賴

          由于 jdk 和 tomcat 這兩者沒有明顯的報錯原因,所以先去排查類的加載機(jī)制


          類加載機(jī)制

          復(fù)習(xí)一下類加載機(jī)制

          我們寫的 Java 應(yīng)用代碼,一般是通過?App ClassLoader?應(yīng)用加載器進(jìn)行加載,它不會自己先去加載它,而是通過?Extension ClassLoader?擴(kuò)展類加載器進(jìn)行加載(其中擴(kuò)展類加載器又會去找?Bootstrap ClassLoader?啟動類加載器進(jìn)行加載),只有父加載器無法加載情況下,才會讓下級加載器進(jìn)行加載。

          引用自zthgreat

          當(dāng)一個ClassLoader實例需要加載某個類時,它會試圖親自搜索某個類之前,先把這個任務(wù)委托給它的父類加載器,這個過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒加載到,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,如果也沒加載到,則轉(zhuǎn)交給App ClassLoader 進(jìn)行加載,如果它也沒有加載得到的話,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類。如果它們都沒有加載到這個類時,則拋出ClassNotFoundException異常。


          ClassLoader

          Java 使用的是雙親委派加載機(jī)制,通過查看?ClassLoader?類,可以對此有所了解。

          類被成功加載后,將被放入到內(nèi)存中,內(nèi)存中存放 Class 實例對象。

          protected?Class?loadClass(String?name,?boolean?resolve)
          ????throws?ClassNotFoundException
          {
          ????synchronized?(getClassLoadingLock(name))?{
          ????????//?First,?check?if?the?class?has?already?been?loaded
          ????????//?首先,檢查?class?是否已經(jīng)被加載
          ????????Class?c?=?findLoadedClass(name);
          ????????if?(c?==?null)?{
          ????????????//?如果沒有被加載
          ????????????long?t0?=?System.nanoTime();
          ????????????try?{
          ????????????????if?(parent?!=?null)?{
          ????????????????????//?尋找?parent?加載器
          ????????????????????c?=?parent.loadClass(name,?false);
          ????????????????}?else?{
          ????????????????????//?如果父加載器不存在,則委托給啟動類加載器加載
          ????????????????????c?=?findBootstrapClassOrNull(name);
          ????????????????}
          ????????????}?catch?(ClassNotFoundException?e)?{
          ????????????????//?ClassNotFoundException?thrown?if?class?not?found
          ????????????????//?from?the?non-null?parent?class?loader
          ????????????}
          ????????????if?(c?==?null)?{
          ????????????????//?If?still?not?found,?then?invoke?findClass?in?order
          ????????????????//?to?find?the?class.
          ????????????????//?如果仍然無法加載,才會嘗試自身加載
          ????????????????long?t1?=?System.nanoTime();
          ????????????????c?=?findClass(name);
          ????????????????//?this?is?the?defining?class?loader;?record?the?stats
          ????????????????sun.misc.PerfCounter.getParentDelegationTime().addTime(t1?-?t0);
          ????????????????sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
          ????????????????sun.misc.PerfCounter.getFindClasses().increment();
          ????????????}
          ????????}
          ????????if?(resolve)?{
          ????????????resolveClass(c);
          ????????}
          ????????return?c;
          ????}
          }

          類加載順序

          從代碼中了解到,如果某個名字的類被加載后,類加載器是不會再重新加載,所以我們的問題根本原因可以是出現(xiàn)在:

          先加載了?org.slf4j?包的?org.slf4j.impl.StaticLoggerBinder,同名的?ch.qos.logback?包下的?StaticLoggerBinder?類沒有被加載

          擴(kuò)展:看完這篇后,別再說你不懂JVM類加載機(jī)制了~

          通過查閱文章:

          跟JAR文件的文件名有關(guān)。按照字母的順序加載JAR文件。有了這個類以后,后面的類則不會加載了。

          jvm 加載包名和類名相同的類時,先加載classpath中jar路徑放在前面的,包名類名都相同,那jvm沒法區(qū)分了,如果使用ide一般情況下是會提示發(fā)生沖突而報錯,若不報錯,只有第一個包被引入(在classpath路徑下排在前面的包),第二個包會在classloader加載類時判斷重復(fù)而忽略。


          查看加載順序

          在 jvm 啟動腳本中,添加?-verbose?參數(shù)或者?-XX:+TraceClassLoading

          [Loaded?java.lang.CloneNotSupportedException?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.lang.Thread$State?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.util.TreeMap$NavigableSubMap?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.util.TreeMap$AscendingSubMap?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.util.TreeMap$NavigableSubMap$EntrySetView?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.util.TreeMap$AscendingSubMap$AscendingEntrySetView?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.util.TreeMap$NavigableSubMap$SubMapIterator?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]
          [Loaded?java.util.TreeMap$NavigableSubMap$SubMapEntryIterator?from?/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]

          之前在本地開發(fā)中,IDEA 優(yōu)化先加載了?ch.qos.logback?的?StaticLoggerBinder?類,然后后面的?org.slf4j?包下的同名類就沒有被加載。

          但這樣也有個不明白,按理說加載順序按照字母順序加載,預(yù)發(fā)環(huán)境還是能夠跟本地開發(fā)一樣,加載到我們需要的類。實際上,加載器加載到的是另一個類,導(dǎo)致應(yīng)用無法啟動。

          通過查找資料

          問題就是jar的加載順序問題,而這個順序?qū)嶋H上是由文件系統(tǒng)決定的,linux內(nèi)部是用inode來指示文件的。

          這種儲存文件元信息的區(qū)域就叫做inode,中文譯名為”索引節(jié)點(diǎn)”。每一個文件都有對應(yīng)的inode,里面包含了與該文件有關(guān)的一些信息。

          Unix/linux系統(tǒng)內(nèi)部不使用文件名,而使用inode號碼來識別文件。對于系統(tǒng)來說,文件名只是inode號碼便于識別的別稱或者綽號。

          為了驗證?inode?是否是問題的原因,我做了以下測試:


          inode 測試加載順序

          本地 Tomcat8 測試(正常啟動)

          將之前在 uat 環(huán)境有問題的代碼版本重新打包,不使用 idea 工具,直接用 tomcat8 啟動,并且在?catalina.sh?腳本中加入類加載打印參數(shù)?-XX:+TraceClassLoading

          catalina.sh

          #?Register?custom?URL?handlers
          #?Do?this?here?so?custom?URL?handles?(specifically?'war:...')?can?be?used?in?the?security?policy
          JAVA_OPTS="$JAVA_OPTS?-XX:+TraceClassLoading"

          查看?catalina.out?輸入日志,發(fā)現(xiàn)先加載的是 logback 包中?StaticLoggerBinder

          在?WEB-INF/lib?下比較 inode 大?。ㄕ=鈮汉蛦?logback < slf4j)

          ll?-i?logback-classic-1.1.3.jar?slf4j-log4j12-1.6.1.jar
          34153162?-rw-r-----??1?jingqi??staff???274K??8??1??2018?logback-classic-1.1.3.jar
          34153180?-rw-r-----??1?jingqi??staff???9.5K?10?17??2018?slf4j-log4j12-1.6.1.jar

          本地 Tomcat8 測試(刪包,先添加 slf4j,后添加 logback)

          • 清理掉 catalina.out

          • 重新上傳包

          • 比較 inode 大小

          • 重新啟動,查看類加載日志

          比較 inode 大?。òl(fā)現(xiàn) slf4j < logback)

          #?ll?-i?logback-classic-1.1.3.jar?slf4j-log4j12-1.6.1.jar
          34162396?-rw-r--r--??1?jingqi??staff???274K??8??1??2018?logback-classic-1.1.3.jar
          34162361?-rw-r--r--??1?jingqi??staff???9.5K?10?17??2018?slf4j-log4j12-1.6.1.jar

          重新啟動后,查看?catalina.out?日志,發(fā)現(xiàn)類加載順序與之前的一致,應(yīng)用也能正常啟動,所以本地開發(fā)無法復(fù)現(xiàn) =-=


          在 uat 環(huán)境服務(wù)器測試

          在?WEB-INF/lib?路徑下,先將這兩個包刪掉,嘗試有不同的上傳順序,模擬 tomcat 解壓 war 包

          [admin@uat-96-0-248?lib]$?rm?logback-classic-1.1.3.jar??slf4j-log4j12-1.6.1.jar
          [admin@uat-96-0-248?lib]$?rz
          [admin@uat-96-0-248?lib]$?#?Received?/Users/jingqi/Downloads/slf4j-log4j12-1.6.1.jar
          [admin@uat-96-0-248?lib]$?rz
          [admin@uat-96-0-248?lib]$?#?Received?/Users/jingqi/Downloads/logback-classic-1.1.3.jar
          #?第一次上傳順序?1、slf4j-log4j12-1.6.1.jar?2、logback-classic-1.1.3.jar
          # inode 比較:slf4j < logback
          [admin@uat-96-0-248?lib]$?ll?-i?logback-classic-1.1.3.jar?slf4j-log4j12-1.6.1.jar
          396731?-rw-r--r--?1?admin?admin?280928?8月???1?2018?logback-classic-1.1.3.jar
          394075?-rw-r--r--?1?admin?admin???9753?10月?17?2018?slf4j-log4j12-1.6.1.jar
          [admin@uat-96-0-248?lib]$?rm?logback-classic-1.1.3.jar??slf4j-log4j12-1.6.1.jar
          [admin@uat-96-0-248?lib]$?rz
          [admin@uat-96-0-248?lib]$?#?Received?/Users/jingqi/Downloads/logback-classic-1.1.3.jar
          [admin@uat-96-0-248?lib]$?rz
          [admin@uat-96-0-248?lib]$?#?Received?/Users/jingqi/Downloads/slf4j-log4j12-1.6.1.jar
          #?第二次上傳順序?1、logback-classic-1.1.3.jar?2、slf4j-log4j12-1.6.1.jar
          # inode 比較:logback < slf4j
          [admin@uat-96-0-248?lib]$?ll?-i?logback-classic-1.1.3.jar?slf4j-log4j12-1.6.1.jar
          394075?-rw-r--r--?1?admin?admin?280928?8月???1?2018?logback-classic-1.1.3.jar
          396731?-rw-r--r--?1?admin?admin???9753?10月?17?2018?slf4j-log4j12-1.6.1.jar

          分別測試了兩種場景,發(fā)現(xiàn)只要這兩個包都存在的情況下,無論?inode?兩者的大小,都是先加載了?slf4j?包的類,導(dǎo)致啟動報錯


          測試結(jié)束

          通過多種測試場景,發(fā)現(xiàn)本地開發(fā)、測試環(huán)境都無法復(fù)現(xiàn)的問題,在 uat 環(huán)境下,只要這兩個包同時存在,都會啟動報錯,系統(tǒng)版本是這個:

          [admin@uat-96-0-248?lib]$?cat?/proc/version
          Linux?version?3.10.0-514.26.2.el7.x86_64?([email protected])?(gcc?version?4.8.5?20150623?(Red?Hat?4.8.5-11)?(GCC)?)?#1?SMP?Tue?Jul?4?15:04:05?UTC?2017
          #?jdk?和?tomcat?版本
          Server?version:?Apache?Tomcat/8.5.34
          Server?built:???Sep?4?2018?22:28:22?UTC
          Server?number:??8.5.34.0
          OS?Name:????????Linux
          OS?Version:?????3.10.0-514.26.2.el7.x86_64
          Architecture:???amd64
          JVM?Version:????1.8.0_192-b12
          JVM?Vendor:?????Oracle?Corporation

          最后在官方文檔發(fā)現(xiàn)這個:

          The order in which the JAR files in a directory are enumerated in the expanded class path is not specified and may vary from platform to platform and even from moment to moment on the same machine. A well-constructed application should not depend upon any particular order. If a specific order is required, then the JAR files can be enumerated explicitly in the class path.

          大意為:同一個目錄下,jvm加載jar包順序是無法保證的,每個系統(tǒng)的都不一樣,甚至同一個系統(tǒng)不同的時刻加載都不一樣。

          于是乎,我也不糾結(jié)某臺服務(wù)器上的類加載順序,在開發(fā)階段就先將這個包沖突的情況,給提前解決掉~


          總結(jié)

          沖突提示信息

          • java.lang.ClassNotFoundException:類型轉(zhuǎn)換錯誤,這個報錯跟我這次遇到的一樣,本應(yīng)該引入的是?logback?包的類,但是實際引入的是?slf4j?下的同名類,導(dǎo)致類型轉(zhuǎn)換錯誤

          • java.lang.NoSuchMethodError:找不到特定方法,如果有兩個同名的包但是不同版本,例如 xxx-1.1和 xxx-1.2包同時存在,先加載了 1.1 版本的類,但是 1.2 版本中才提供了新方法,導(dǎo)致提示找不到特定方法

          • java.lang.NoClassDefFoundError,java.lang.LinkageError

          排查思路

          1、查看?catalina.sh?堆棧信息,找到有問題的類

          2、通過 IDEA ,在打包的 POM 文件中,使用?Maven Helper?插件找出沖突的依賴,確定項目需要的 jar 包,Exclude?掉不需要的依賴。

          提前預(yù)防

          1、使用工具檢查依賴沖突

          沖突檢測插件 :maven-enforcer-plugin

          引用新的第三方依賴(工具包或者框架包),通過 Maven 插件檢查一下 conflict 依賴,提前進(jìn)行 Exclude

          工具檢測:解決Maven依賴沖突的好幫手,這款I(lǐng)DEA插件了解一下?

          2、統(tǒng)一服務(wù)器版本

          在測試階段,準(zhǔn)備好和生產(chǎn)環(huán)境一樣的服務(wù)器,提前進(jìn)行測試,避免依賴沖突的?WAR?包上傳到生產(chǎn)環(huán)境,例如我們有一臺 UAT 服務(wù)器,與生產(chǎn)環(huán)境一樣配置,提前測試,暴露風(fēng)險和解決問題~


          參考資料

          • https://blog.csdn.net/u011372108/article/details/83824274
          • https://blog.csdn.net/u014634338/article/details/81434327
          • https://blog.csdn.net/hnzmdpan/article/details/78637635
          • https://blog.csdn.net/chufuying3/article/details/47007595
          • https://www.cnblogs.com/saaav/p/7716179.html
          • https://my.oschina.net/u/3386233/blog/1476822
          • http://www.voidcn.com/article/p-qncymgza-brh.html
          • https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/classpath.html

          END


          有熱門推薦?

          1.?基于 SpringBoot,來實現(xiàn)MySQL讀寫分離技術(shù)

          2.?超火的釘釘群聊機(jī)器人原來這么設(shè)置的

          3.?Java 項目權(quán)威排名:Nacos 未上版,Gradle 排名第二,Maven 排名 28

          4.?SpringBoot中使用注解來實現(xiàn) Redis 分布式鎖

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 45
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费播放的a片 | 亚洲一区二区三区 | 大鸡吧av网站 | 大香蕉网成人电影 | 日韩肏屄网|