java.lang.NoSuchMethodError問題分析

1、問題引出:

今天在項目中,發現報了java.lang.NoSuchMethodError這個錯誤 ,錯誤信息以下:
Caused by: java.lang.NoSuchMethodError: com.google.common.base.Objects.firstNonNull(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    at com.google.common.cache.CacheBuilder.getKeyStrength(CacheBuilder.java:529)java

根據異常信息咱們大概能夠了解,工程中引用了一個名叫 com.google.common.cache.CacheBuilder 的類,並調用了其中名爲 getKeyStrength 的方法,以下:web

Strength getKeyStrength() {
    return (Strength)Objects.firstNonNull(this.keyStrength, Strength.STRONG);
}

該方法又調用了com.google.common.base.Objects.firstNonNull這個方法,可是系統在加載該方法時卻表示沒有找到該方法。在IDEA裏面輸入com.google.common.base.Objects這個類名出來了兩個:緩存

點進去發現,guava-14.0.1的com.google.common.base.Objects類實現是有這個方法的,以下:maven

public static <T> T firstNonNull(@Nullable T first, @Nullable T second) {
    return first != null ? first : Preconditions.checkNotNull(second);
}

可是在google-collection-1.0.jar包中的com.google.common.base.Objects卻沒有這個實現,其內容以下:ide

package com.google.common.base;

import com.google.common.annotations.GwtCompatible;
import java.util.Arrays;
import javax.annotation.Nullable;

@GwtCompatible
public final class Objects {
    private Objects() {
    }

    public static boolean equal(@Nullable Object a, @Nullable Object b) {
        return a == b || a != null && a.equals(b);
    }

    public static int hashCode(Object... objects) {
        return Arrays.hashCode(objects);
    }
}

得出結論,這是一個包衝突問題,工具

2、問題解決:

當遇到這類問題咱們該如何解決呢,主要有如下三步:測試

一、 發現是哪一個類發生了衝突:

前面已經分析過,是com.google.common.base.Objects這個類的衝突ui

二、發現衝突 jar 包,即衝突類存在於哪一個 Jar 包中:

見前面分析,發現是google-collection-1.0.jar和guava-14.0.1.jar包衝突this

三、發現這個衝突 Jar 包是自身系統直接引用的仍是系統引用的 Jar 間接引用的:

第三步,咱們看 google-collections-1.0.jar 是不是應用直接引用的。google

經過查看這兩個包的引用發現,工程裏面直接引用了google-collections這個jar包,以下:

<dependency>
    <groupId>com.google.collections</groupId>
    <artifactId>google-collections</artifactId>
</dependency>

,沒有發現直接引用guava.jar包,所以guava.jar包是經過其餘jar包間接引用過來的。

咱們的項目是經過 maven 進行引用 jar 包的管理。所以,結合 maven 的命令 mvn dependency:tree 能夠發現這兩個jar 包究竟是經過哪些jar 包間接引用進來的。

       經查詢資料發現,guava.jar包是google-collections的升級版,包括後者,因此去掉google-collections的包引入,重啓工程,問題解決。

3、問題分析與拓展

      這類狀況的發生極可能是由於以下情況引發的:隨着業務需求的不斷擴展,應用中代碼量也會逐漸增加,工程中引用的二方包或者三方包也天然而然會愈來愈多。所以,不可避免,可能存在引用的二方包或三方包相互衝突所致使的系統問題。

通常經過以下方法,能夠定位並決絕包衝突的問題:

一、mvn dependency:tree

這個命令能夠輸出全部的依賴,若是加入一些參數,能夠過濾一些東西,如

-Dverbose

-Dincludes

-Dexcludes  等參數會可以幫助儘快定位問題

二、IDEA的jar包依賴分析,再次再也不贅述

三、本身寫一個測試類,來查看加載的究竟是哪一個jar包:


有時,你覺得解決了,可是恰恰仍是報類包衝突(典型症狀是java.lang.ClassNotFoundException或Method不兼容等異常),這時你能夠設置一個斷點,在斷點處經過下面這個我作的工具類來查看Class所來源的JAR包:

    package com.ridge.util;  
      
    import java.io.File;  
    import java.net.MalformedURLException;  
    import java.net.URL;  
    import java.security.CodeSource;  
    import java.security.ProtectionDomain;  
      
    /** 
     * @author : chenxh 
     * @date: 13-10-31 
     */  
    public class ClassLocationUtils {  
      
        /** 
         * 獲取類全部的路徑 
         * @param cls 
         * @return 
         */  
        public static String where(final Class cls) {  
            if (cls == null)throw new IllegalArgumentException("null input: cls");  
            URL result = null;  
            final String clsAsResource = cls.getName().replace('.', '/').concat(".class");  
            final ProtectionDomain pd = cls.getProtectionDomain();  
            if (pd != null) {  
                final CodeSource cs = pd.getCodeSource();  
                if (cs != null) result = cs.getLocation();  
                if (result != null) {  
                    if ("file".equals(result.getProtocol())) {  
                        try {  
                            if (result.toExternalForm().endsWith(".jar") ||  
                                    result.toExternalForm().endsWith(".zip"))  
                                result = new URL("jar:".concat(result.toExternalForm())  
                                        .concat("!/").concat(clsAsResource));  
                            else if (new File(result.getFile()).isDirectory())  
                                result = new URL(result, clsAsResource);  
                        }  
                        catch (MalformedURLException ignore) {}  
                    }  
                }  
            }  
            if (result == null) {  
                final ClassLoader clsLoader = cls.getClassLoader();  
                result = clsLoader != null ?  
                        clsLoader.getResource(clsAsResource) :  
                        ClassLoader.getSystemResource(clsAsResource);  
            }  
            return result.toString();  
        }  
      
    }  


隨便寫一個測試,設置好斷點,在執行到斷點處按alt+F8動態執行代碼(intelij idea),假設咱們輸入:
Java代碼  收藏代碼

ClassLocationUtils.where(org.objectweb.asm.ClassVisitor.class)  

便可立刻查出類對應的JAR了:

這就是org.objectweb.asm.ClassVisitor類在運行期對應的JAR包,若是這個JAR包版本不是你指望你,就說明是你的IDE緩存形成的,這時建議你Reimport一下maven列表就能夠了,以下所示(idea):

四、有的時候多是IDE的問題:

(1)Reimport一下,IDE會強制根據新的pom.xml設置從新分析並加載依賴類包,以獲得和pom.xml設置相同的依賴。
(2)idea清除緩存,採用idea自帶的功能,File->Invalidate Caches 功能直接完成清除idea cache

參考文獻:http://fufeng.iteye.com/blog/1755167

http://blog.csdn.net/sun_wangdong/article/details/51852113

相關文章
相關標籤/搜索