jar包衝突解決的一些經驗

開篇

 最近因爲一些原因遇到了一些jar包衝突的實際問題,包括tomcat無法加載某lib包,hadoop上mr任務依賴lib版本問題,tomcat依賴包找不到等等。

 這些問題歸結起來能夠很好的檢驗幾個關鍵問題:1、雙親委派原則的理解;2、maven依賴傳遞的原則;3、java classpath路徑先後順序問題。


雙親委派原則

說明:

  • 1、首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接返回。
  • 2、如果此類沒有加載過,那麼,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類加載器來加載。
  • 3、如果父加載器及bootstrap類加載器都沒有找到指定的類,那麼調用當前類加載器的findClass方法來完成類加載。
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    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;
    }
}

實際案列

  • 如果指定名稱的類已經加載過了就不會再次加載,這個問題屬於雙親委派範圍,存在兩種情況會遇到這個問題。
  • 1、多版本jar包依賴當中,由於低版本jar包中類已經加載導致高版本jar包中同名的類無法加載,從而導致使用了高版本的jar包依賴的函數無法找到,因爲該函數只在高版本中存在。
  • 2、不同的jar包卻存在同名路徑的類,如package+class的路徑是完全一致的,那就會導致兩個不同的jar包的同名類只有一個類會被加載,從而同樣找不到函數問題。


maven依賴原則

maven的依賴順序

  • 1、依賴路徑最短優先原則
    一個項目Demo依賴了兩個jar包,其中A-B-C-X(1.0) , A-D-X(2.0)。由於X(2.0)路徑最短,所以項目使用的是X(2.0)。
  • 2、pom文件中申明順序優先
    如果A-B-X(1.0) ,A-C-X(2.0) 這樣的路徑長度一樣怎麼辦呢?這樣的情況下,maven會根據pom文件聲明的順序加載,如果先聲明瞭B,後聲明瞭C,那就最後的依賴就會是X(1.0)。
  • 3、覆寫優先
    子pom內聲明的優先於父pom中的依賴。

maven的解決衝突

  • 1、通過們mvn dependency:tree查看依賴樹,通過maven的依賴原則來調整座標在pom文件的申明順序是最好的辦法。
  • 2、通過exclude排除一些間接依賴,或者把依賴的jar包調整到最前面,按照maven加載順序最前面加載了jar包後間接引用就不會生效了。


classpath路徑先後問題

Of particular importance, and much consternation, 
the class loader will load classes in the order they appear in the classpath. 
Starting with the first classpath entry, 
the class loader visits each specified directory or archive file attempting to find the class to load. 
The first class it finds with the proper name is loaded, and any remaining classpath entries are ignored.

說明:

  • 上面說明了,java xx -classpath ".;a.jar;b.jar;" 時,如果a.jar和b.jar有重名的類,那麼會以a.jar的爲準,忽略b.jar的,因爲jvm按照-classpath參數的路徑先後順序去load類,後續加載的同名的類會被忽略。

案例分享

  • hadoop集羣上跑MR任務(自行打的jar包,jar包中依賴的protobuf-java版本高於hadoop集羣自身lib目錄當中的protobuf-java版本),導致每次先加載低版本的protobuf-java版本然後報方法找不到錯誤。
  • 實際當中針對上面的問題,會在把高版本的jar包放在當前目錄並在代碼當中把當前目錄添加到classpath的最前面,保證先加載高版本的jar包。


tomcat無法加載jar包

真實案例

錯誤信息
 java.lang.NoClassDefFoundError: com/beibei/ai/base/UserModel

catalina.log日誌內容
Apr 5, 2013 1:38:26 PM org.apache.catalina.loader.WebappClassLoader validateJarFile
INFO: validateJarFile(/home/frodo/apache-tomcat-7.0.37/webapps/hive/WEB-INF/lib/base-0.0.42-SNAPSHOT.jar) - jar not loaded. 
See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class

說明:

  • 查看/logs/catalina.log定位到具體的日誌。
  • 和tomcat當中加載的類javax.servlet.Servlet發生衝突導致類無法加載。
  • Stack Overflow問題
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章