1. 程式人生 > >架構探險(三)自定義類載入器

架構探險(三)自定義類載入器

今天我們來談一談架構探險中自定義的類載入器,一般我們若想實現自定義的類載入器,可以繼承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檔案,筆者在註釋中寫出來自己的一些疑問,歡迎大家分享自己的見解。