問題
在編碼過程中,往往會遇到jar包衝突的問題。問題的表現特徵一般都是拋出java.lang.NoSuchMethodError異常。那麼,今天就聊聊怎麼解決此類問題。 分析
此問題跟java的類載入機制有關。JVM裝載類時使用“全盤負責委託機制”,此問題跟“委託機制”有關。它是指先委託父裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中尋找並裝載目標類。
然而,如果在類路徑下放置了多個不同版本的類包,如commons-lang 2.x.jar和commons-lang3.x.jar都位於類路徑中,代碼中用到了commons-lang3.x類的某個方法,而這個方法在commons-lang2.x中並不存在,JVM載入類時碰巧又從commons-lang 2.x.jar中載入類,運行時就會拋出NoSuchMethodError的錯誤。 問題排查
這種問題的排查是比較棘手的,特別是在Web應用的情況下,可作為類路徑的系統目錄比較多,特別在類包眾多時,情況尤其複雜:你不知道JVM到底從哪個類包中載入類檔案。
下面提供一個srcAdd.jsp的程式,你把它放到Web應用的根路徑下,通過如下方式即可查看JVM從哪個類包載入指定類(其中className為className參數為類的全名,不需要.class尾碼,連接埠號碼為web項目的連接埠號碼,此處為80):
http://localhost/srcAdd.jsp?className=java.net.URL
srcAdd.jsp源碼如下:
<%@page contentType="text/html; charset=GBK"%><%@page import="java.security.*,java.net.*,java.io.*"%><%! public static URL getClassLocation(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(); // java.lang.Class contract does not specify if 'pd' can ever be null; // it is not the case for Sun's implementations, but guard against null // just in case: if (pd != null) { final CodeSource cs = pd.getCodeSource(); // 'cs' can be null depending on the classloader behavior: if (cs != null) result = cs.getLocation(); if (result != null) { // Convert a code source location into a full class file location // for some common cases: 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) { // Try to find 'cls' definition as a resource; this is not // document.d to be legal, but Sun's implementations seem to //allow this: final ClassLoader clsLoader = cls.getClassLoader(); result = clsLoader != null ? clsLoader.getResource(clsAsResource) : ClassLoader.getSystemResource(clsAsResource); } return result; }%><html><head><title>srcAdd.jar</title></head><body bgcolor="#ffffff"> 使用方法,className參數為類的全名,不需要.class尾碼,如 srcAdd.jsp?className=java.net.URL<%try{ String classLocation = null; String error = null; String className = request.getParameter("className"); classLocation = ""+getClassLocation(Class.forName(className)); if (error == null) { out.print("類" + className + "執行個體的物理檔案位於:"); out.print("<hr>"); out.print(classLocation); } else { out.print("類" + className + "沒有對應的物理檔案。<br>"); out.print("錯誤:" + error); }}catch(Exception e){ out.print("異常。"+e.getMessage());}%></body></html>