架構探險(三)自定義類載入器
阿新 • • 發佈:2018-11-29
今天我們來談一談架構探險中自定義的類載入器,一般我們若想實現自定義的類載入器,可以繼承ClassLoader類,然後實現findClass方法即可,詳細介紹可以看以下連結:
https://www.cnblogs.com/doit8791/p/5820037.html
本文主要談一下架構探險中的實現方式。在getClassLoader方法中我們拿到的實際上時當前執行緒的ClassLoader。然後loadClass則直接呼叫Class.forName()方法,loadClass方法實現了類載入。核心的方法是怎麼掃描一個包檔案下的所有class檔案。具體實現請看程式碼:
public static ClassLoader getClassLoader() { //這裡我們獲取當前執行緒的類載入器 return Thread.currentThread().getContextClassLoader(); } /** * 載入類 * @param className * @param isInitialized * @return */ public static Class<?> loadClass(String className, boolean isInitialized) { Class<?> cls = null; try { cls = Class.forName(className, isInitialized, getClassLoader()); } catch (ClassNotFoundException ex) { logger.error("ClassNotFoundException", className); } return cls; } /** * 獲取指定包名下的所有類 * @param packageName * @return */ public static Set<Class<?>> getClassSet(String packageName) { Set<Class<?>> classSet = new HashSet<>(); try { Enumeration<URL> urlEnumeration = getClassLoader().getResources(packageName.replace(".", "/")); while (urlEnumeration.hasMoreElements()) { URL url = urlEnumeration.nextElement(); if(url != null) { String protocol = url.getProtocol(); if(protocol.equals("file")) { String packagePath = url.getPath().replace("%20", ""); addClass(classSet, packagePath, packageName); } else if(protocol.equals("jar")) { // ?? // 可能永遠執行不到,經測試如果直接把jar檔案,copy到src路徑下,則讀取到jar時所用的protocol依然是file. // JarUrlConnection 必須使用URL url = new Url("jar:file:/xxx.jar!/"); // ?? // 如果包含jar 我們需要提取jar裡的class檔案, 這裡用的時JarURLConnection // 這裡還有其他選擇,我們可以使用fastjson來提取jar檔案 JarURLConnection jarUrlConn = (JarURLConnection) url.openConnection(); if(jarUrlConn != null) { JarFile jarFile = jarUrlConn.getJarFile(); Enumeration<JarEntry> entities = jarFile.entries(); while (entities.hasMoreElements()) { JarEntry jarEntity = entities.nextElement(); String jarEntityName = jarEntity.getName(); if(jarEntityName.endsWith(".class")) { String className = jarEntityName.substring(0, jarEntityName.lastIndexOf(".")).replace("/", "."); doAddClass(classSet, className); } } } } } } } catch (IOException ex) { logger.error("get class set failure", ex); throw new RuntimeException(ex); } return classSet; } private static void addClass(Set<Class<?>> classSet, String packagePath, final String packageName) { final File[] files = new File(packagePath).listFiles(new FileFilter() { @Override public boolean accept(File file) { return (file.exists() && file.getName().endsWith(".class")) || file.isDirectory(); } }); for(File file : files) { String fileName = file.getName(); if(file.isFile()) { String className = fileName.substring(0, fileName.lastIndexOf(".")); if(StringUtils.isNotEmpty(packageName)) { className = String.format("%s.%s", packageName, className); } doAddClass(classSet, className); } else { // 如果是目錄,繼續掃描 String subPackagePath = fileName; if(StringUtils.isNotEmpty(packagePath)) { //作為新的掃描路徑 subPackagePath = String.format("%s/%s", packagePath, fileName); } String subPackageName = fileName; if(StringUtils.isNotEmpty(packageName)) { //掃描新的包,因為載入類需要全路徑,所以我們需要不斷拼接 subPackageName = String.format("%s.%s", packageName, subPackageName); } addClass(classSet, subPackagePath, subPackageName); } } } private static void doAddClass(Set<Class<?>> classSet, String className) { Class<?> clazz = loadClass(className, false); classSet.add(clazz); }
程式碼關於如何獲取jar檔案中的class檔案,筆者在註釋中寫出來自己的一些疑問,歡迎大家分享自己的見解。