JAVA的父類委托加載機制,再帶來巨大便利性和效率提升的同時的同時也帶來不少麻煩,最直接的就是類沖突造成的問題,以下場景不知道諸位是不是有點熟悉。
本文定義的類沖突定義為相同命名空間下的class分散在不通的jar包之中。
1、造成的注入系統混亂。
2、造成類型判斷系統混亂,例如 if ((paramObject instanceof CLASSS))判斷失靈
3、不同版本class實現方法有升級 例如Ajar包支持getXX(A,B),而另外jar中卻只有getXX(A)
4、在數據在運算中的神秘失蹤,如方法A jar中有方法void A(B b),C包中調用A的方法傳入的對象 b和Ajar中的B加載的是有類沖突的B。運算結果可以想而知。
這種現象造成的一個問題就是程序員回說我的代碼沒有問題,我本地也是正常的....,之類的神奇現象,下面嘗試去解決一下這幾個問題。
1、個人認為首先要對類加載機制有個適當的了解。
以當前比較流行的tomcat為例:加載順序個人認為講的比較詳細的查閱。
2、定位一下我的classpath或者項目中會從那幾個路徑中加載,然后找出
我的程序到底加載的是哪個呢?
可以用該方法在文件中找出有哪些類有可能造成沖突。
import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.io.*; public class JarFinder { public static void FindClassInJar(String jarName) throws IOException { String filePath = jarName; if (filePath.endsWith(".jar")) { } else return; java.util.jar.JarFile file = new JarFile(filePath); Enumeration<JarEntry> entrys = file.entries(); while (entrys.hasMoreElements()) { JarEntry jar = entrys.nextElement(); String tmpJarName = jar.getName(); tmpJarName = tmpJarName.replace('/', '.'); if (tmpJarName.contains("javax.mail.Multipart")) { System.out.println(tmpJarName + " " + file.getName()); } } file.close(); } final static void ShowAllFileInDir(File dir) throws Exception { File[] fs = dir.listFiles(); for (int i = 0; i < fs.length; i++) { String file = fs[i].getAbsolutePath(); FindClassInJar(file); if (fs[i].isDirectory()) { try { ShowAllFileInDir(fs[i]); } catch (Exception e) { } } } } public static void main(String[] args) throws Exception { File root = new File("C:/Program Files/Java/jdk1.8.0_102/jre/lib/ext"); ShowAllFileInDir(root); } }
3、減少相關jar包的數量
1、類統一,比如部署在tomcat上的不同項目每個項目多有jar A,那么不妨把jar A放在tomcat的/common/lib目錄下。
2、盡量把能去掉的jar從項目中移除出去
此方法通常可以解決一大部分問題,個人認為也是解決這類問題的一個關鍵思路。
4、代碼版本統一
解決問題的最好辦法就是預防。部署在同一個tomcat下的項目使用的基礎jar包要盡量統一,從制度和規范上解決這個問題。最好能一個公司統一的依賴庫,maven是個不錯的管理方式,公司按照統一的步調處理依賴項。
5、對於不能移除的可以通過控制jar包加載的順序

6、確認不需要的jar包是否已經真從相關路徑中移除。
個人就曾遇到從項目的依賴項中把jar去掉了,但是lib路徑下仍存在這個jar導致的仍然被打到包里去了,活活郁悶兩天。
其他有可能用到定位class路徑的方法:
public static String getProjectPath() { java.net.URL url = oracle.sql.NCLOB.class.getProtectionDomain().getCodeSource().getLocation(); String filePath = null; try { filePath = java.net.URLDecoder.decode(url.getPath(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } if (filePath.endsWith(".jar")) filePath = filePath.substring(0, filePath.lastIndexOf("/") + 1); java.io.File file = new java.io.File(filePath); filePath = file.getPath(); return filePath; } public static String getRealPath() { String realPath = oracle.sql.NCLOB.class.getClassLoader().getResource("").getFile(); //java.io.File file = new java.io.File(realPath); //realPath = file.(); System.out.println(realPath); try { realPath = java.net.URLDecoder.decode(realPath, "utf-8"); } catch (Exception e) { e.printStackTrace(); } return realPath; } public static String getAppPath(Class<?> cls) { // 檢查用戶傳入的參數是否為空 if (cls == null) throw new java.lang.IllegalArgumentException("參數不能為空!"); ClassLoader loader = cls.getClassLoader(); // 獲得類的全名,包括包名 String clsName = cls.getName(); // 此處簡單判定是否是Java基礎類庫,防止用戶傳入JDK內置的類庫 if (clsName.startsWith("java.") || clsName.startsWith("javax.")) { throw new java.lang.IllegalArgumentException("不要傳送系統類!"); } // 將類的class文件全名改為路徑形式 String clsPath = clsName.replace(".", "/") + ".class"; System.out.println(clsPath); // 調用ClassLoader的getResource方法,傳入包含路徑信息的類文件名 java.net.URL url = loader.getResource(clsPath); // 從URL對象中獲取路徑信息 String realPath = url.getPath(); System.out.println(realPath); // 去掉路徑信息中的協議名"file:" int pos = realPath.indexOf("file:"); if (pos > -1) { realPath = realPath.substring(pos + 5); } //System.out.println(realPath); // 去掉路徑信息最后包含類文件信息的部分,得到類所在的路徑 //pos = realPath.indexOf(clsPath); //realPath = realPath.substring(0, pos - 1); // 如果類文件被打包到JAR等文件中時,去掉對應的JAR等打包文件名 //if (realPath.endsWith("!")) { // realPath = realPath.substring(0, realPath.lastIndexOf("/")); //} //java.io.File file = new java.io.File(realPath); //realPath = file.getAbsolutePath(); try { realPath = java.net.URLDecoder.decode(realPath, "utf-8"); } catch (Exception e) { throw new RuntimeException(e); } return realPath; }
從問題的兩面性來看。這種加載機制也能給我帶來便利性的一面。
比如我們要修改jar A中類B的實現,而我們又沒有源代碼,此時這種加載機制就很有用了。
我們只需要在項目的src中按照B的包名搭建即可。
不過使用此方法時要注意改類影響的范圍,盡量不要在通用的類上執行此操作,否則會造成一些不可控的風險。