一、问题引出:
今天在项目中,发现报了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)根据异常信息我们大概可以了解,工程中引用了一个名叫 com.google.common.cache.CacheBuilder 的类,并调用了其中名为 getKeyStrength 的方法,如下:
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类实现是有这个方法的,如下:
public staticT firstNonNull(@Nullable T first, @Nullable T second) { return first != null ? first : Preconditions.checkNotNull(second);}
但是在google-collection-1.0.jar包中的com.google.common.base.Objects却没有这个实现,其内容如下:
package com.google.common.base;import com.google.common.annotations.GwtCompatible;import java.util.Arrays;import javax.annotation.Nullable;@GwtCompatiblepublic 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); }}
得出结论,这是一个包冲突问题,
二、问题解决:
当遇到这类问题我们该如何解决呢,主要有以下三步:
1、 发现是哪个类发生了冲突:
前面已经分析过,是com.google.common.base.Objects这个类的冲突
2、发现冲突 jar 包,即冲突类存在于哪个 Jar 包中:
见前面分析,发现是google-collection-1.0.jar和guava-14.0.1.jar包冲突
3、发现这个冲突 Jar 包是自身系统直接引用的还是系统引用的 Jar 间接引用的:
第三步,我们看 google-collections-1.0.jar 是否是应用直接引用的。
通过查看这两个包的引用发现,工程里面直接引用了google-collections这个jar包,如下:
com.google.collections google-collections
,没有发现直接引用guava.jar包,因此guava.jar包是通过其他jar包间接引用过来的。
我们的项目是通过 maven 进行引用 jar 包的管理。因此,结合 maven 的命令 mvn dependency:tree 可以发现这两个jar 包到底是通过哪些jar 包间接引用进来的。
经查询资料发现,guava.jar包是google-collections的升级版,包括后者,所以去掉google-collections的包引入,重启工程,问题解决。
三、问题分析与拓展
这类情况的发生很可能是因为如下状况引起的:随着业务需求的不断扩展,应用中代码量也会逐渐增长,工程中引用的二方包或者三方包也自然而然会越来越多。因此,不可避免,可能存在引用的二方包或三方包相互冲突所导致的系统问题。
一般通过如下方法,可以定位并决绝包冲突的问题:
1、mvn dependency:tree
这个命令可以输出所有的依赖,如果加入一些参数,可以过滤一些东西,如
-Dverbose
-Dincludes
-Dexcludes 等参数会能够帮助尽快定位问题
2、IDEA的jar包依赖分析,再次不再赘述
3、自己写一个测试类,来查看加载的到底是哪个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; /** * : chenxh * : 13-10-31 */ public class ClassLocationUtils { /** * 获取类所有的路径 * cls * */ 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):4、有的时候可能是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