自定義ClassLoader的實現

背景

公司使用的是自己實現的RPC框架,有自己的傳輸協議和序列化方式,在RPC服務啓動的時候,會掃描當前服務的lib目錄,然後後加載以com.xxx開頭的class文件,爲接口的返回結果序列化爲java Object做準備。
2019年開始,通過不斷的調研和思考,RPC服務接口測試平臺初具雛形,在開發測試平臺的時候,一直存在一個問題,測試平臺肯定要能夠爲所有的RPC服務都提供測試能力,但是每個RPC服務都有自己的lib目錄,都有自己依賴的jar包和版本,所以每一次測試都需要使用被測服務當前版本依賴的jar包,類似與tomcat的每個應用都是互相隔離的,都使用自己的jar包。

思路

背景中也提到了,tomcat可以實現應用間jar包的隔離,那麼就按照這個思路繼續下去,先介紹一下普通java的雙親委託機制和普通java進程的classloader關係,BootStrap ClassLoader、ExtClassLoader、AppClassLoader爲java的源碼,他們的關係如下圖
可以參考sun.misc.Launcher和java.lang.ClassLoader源碼和博客深入理解 Tomcat(四)Tomcat 類加載器之爲何違背雙親委派模型 【2. 什麼是雙親委任模型】
普通java進程的classloader關係

sun.misc.Launcher.AppClassloader
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    ...
    //如果已知是不存在的類
    if (this.ucp.knownToNotExist(var1)) {
    	// TODO 追到native方法,邏輯下次再補充上來
        Class var5 = this.findLoadedClass(var1);
        if (var5 != null) {
        ...
            return var5;
        } else {
            throw new ClassNotFoundException(var1);
        }
    } else {
    	//委託給父類的loadClass方法加載
        return super.loadClass(var1, var2);
    }
}
java.lang.ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
            	// 如果有父classloader,委託給parent加載
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                // 如果parent爲null,及當前的classloader是ExtClassloader,委託bootstrap classloader加載
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

Tomcat打破了這種加載順序,當前classloader加載類的時候,先嚐試自己加載,加載失敗的時候才委託parent加載,那麼模仿tomcat的org.apache.catalina.loader.WebappClassLoader實現自己的classloader不久可以了?!
Apache Tomcat 8
在這裏插入圖片描述

實現

重載loadClass、findLoadedClass0、findClass、findResources

/**
 * @author [email protected]
 * @version 1.0
 * @date 2020/2/9 19:48
 */
public class TestCaseClassLoader extends URLClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
	    // 加鎖
        synchronized (this.getClassLoadingLockInternal(name)) {
            Class<?> clazz = null;
            // Check our previously loaded local class cache
            // 檢查本地緩存中是否已經加載過
            clazz = this.findLoadedClass0(name);
            if (clazz != null) {
                if (resolve) {
                    this.resolveClass(clazz);
                }
                return clazz;
            }

            //  Check our previously loaded class cache
            // 還沒找到和findLoadedClass0有啥差別
            clazz = this.findLoadedClass(name);
            if (clazz != null) {
                if (resolve) {
                    this.resolveClass(clazz);
                }
                return clazz;
            }

            //  Try loading the class with the system class loader, to prevent   the webapp from overriding J2SE classes
            // Bootstrap Classloader是否加載
            try {
                clazz = this.j2seClassLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve) {
                        this.resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException var12) {
            }

            //  Search local repositories
            // 從當前的classloader 目錄下加載
            try {
                clazz = this.findClass(name);
                if (clazz != null) {
                    if (resolve) {
                        this.resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException var14) {
            }

            // Delegate to parent unconditionally
            // 委託給parent加載
            try {
                clazz = Class.forName(name, false, this.parent);
                if (clazz != null) {
                    if (resolve) {
                        this.resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException var10) {
            }
        }
        throw new ClassNotFoundException(name);
    }

	// 查找緩存
    private Class<?> findLoadedClass0(String name) {
        String path = this.binaryNameToPath(name);
        ResourceEntry entry = (ResourceEntry) this.resourceEntries.get(path);
        return entry != null ? entry.loadedClass : null;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (!this.validate(name)) {
            throw new ClassNotFoundException(name);
        } else {
            String path = this.binaryNameToPath(name);
            // 從指定目錄下查找
            ResourceEntry entry = this.findResourceInternal(name, path);

            if (entry == null) {
                throw new ClassNotFoundException(name);
            } else {
                clazz = entry.loadedClass;
                if (clazz == null) {
                    synchronized (this.getClassLoadingLockInternal(name)) {
                        clazz = entry.loadedClass;
                        if (clazz != null) {
                            return clazz;
                        } else if (entry.binaryContent == null) {
                            throw new ClassNotFoundException(name);
                        } else {
                            try {
                            // 從字節碼轉爲java class對象
                                clazz = this.defineClass(name, entry.binaryContent, 0, entry.binaryContent.length, new CodeSource(entry.codeBase, entry.certificates));
                            } catch (UnsupportedClassVersionError var11) {
                                throw new UnsupportedClassVersionError(name);
                            }
                            entry.loadedClass = clazz;
                            entry.binaryContent = null;
                            entry.certificates = null;
                        }
                    }
                }
            }
        }
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }

    ...

    /**
     * SCFSerializableScanner.scan方法會調用classloader的getResources獲取指定包名的class文件
     *
     * @param name
     * @return
     * @throws IOException
     */
    public Enumeration<URL> findResources(String name) throws IOException {
        if (loggerHelper.isDebugEnabled()) {
            loggerHelper.debug("    findResources(" + name + ")");
        }

        LinkedHashSet<URL> result = new LinkedHashSet();
        int jarFilesLength = this.jarFiles.size();

        synchronized (this.jarFiles) {
            for (int i = 0; i < jarFilesLength; ++i) {
                JarFile jarFile = this.jarFiles.get(i);
                JarEntry jarEntry = jarFile.getJarEntry(name);
                if (jarEntry != null) {
                    try {
                        String jarFakeUrl = this.getURI(new File(jarFile.getName())).toString();
                        result.add(UriUtil.buildJarUrl(jarFakeUrl, name));
                    } catch (MalformedURLException var10) {
                    }
                }
            }
        }

        return Collections.enumeration(result);
    }
}

效果

再測試平臺上每一次測試都新建一個classloader,加載指定目錄下的jar包,做到每次測試都是隔離的

完整源碼

TestCaseClassLoader
import com.common.helper.LoggerHelper;
import org.apache.commons.io.FileUtils;

import javax.naming.NamingException;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.security.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author [email protected]
 * @version 1.0
 * @date 2020/2/9 19:48
 */
public class TestCaseClassLoader extends URLClassLoader {
    private static LoggerHelper loggerHelper = LoggerHelper.getLoggerHelper(TestCaseClassLoader.class);

    private static final Method GET_CLASSLOADING_LOCK_METHOD;
    // 緩存
    private Map<String, ResourceEntry> resourceEntries = new ConcurrentHashMap();
    // jvm根classloader
    private ClassLoader j2seClassLoader;
    // 創建TestCaseClassLoader時指定父classloader
    private ClassLoader parent;
    // 倉庫地址,從哪些目錄下查找
    private String[] repositories;
    protected HashMap<String, String> notFoundResources;
    private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
    // 倉庫地址下的所有jar
    private List<JarFile> jarFiles;

    static {
        Method getClassLoadingLockMethod = null;

        try {
            final Method registerParallel = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable");
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    registerParallel.setAccessible(true);
                    return null;
                }
            });
            registerParallel.invoke((Object) null);
            getClassLoadingLockMethod = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class);
        } catch (Exception var2) {
        }

        GET_CLASSLOADING_LOCK_METHOD = getClassLoadingLockMethod;
    }

    public TestCaseClassLoader(String[] lookupPaths, ClassLoader parent) {
        super(new URL[0], parent);

        this.parent = null;

        ClassLoader p = this.getParent();
        if (p == null) {
            p = getSystemClassLoader();
        }

        this.parent = p;
        ClassLoader j = String.class.getClassLoader();
        // 獲取bootstrap classloader
        if (j == null) {
            for (j = getSystemClassLoader(); j.getParent() != null; j = j.getParent()) {
            }
        }
        this.j2seClassLoader = j;
        this.jarFiles = new ArrayList<>();
        this.repositories = lookupPaths;

        //初始化資源
        initRepositories();

        class NamelessClass_1 extends LinkedHashMap<String, String> {
            private static final long serialVersionUID = 1L;

            NamelessClass_1() {
            }

            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                return this.size() > 1000;
            }
        }

        this.notFoundResources = new NamelessClass_1();
    }

    /**
     * 掃描repositories中指定的目錄下的所有jar包
     */
    private void initRepositories() {
        if (this.repositories.length > 0) {
            for (String s : repositories) {
                File file;
                JarFile jarFile = null;
                try {
                    file = new File(s);
                    Collection<File> jars = FileUtils.listFiles(file, new String[]{"jar"}, true);
                    for (File f : jars) {
                        // 過濾掉源碼文件
                        if (!f.getName().contains("sources")) {
                            String dependentJar = f.getCanonicalPath();
                            jarFile = new JarFile(dependentJar);
                            jarFiles.add(jarFile);
                        }
                    }
                } catch (Exception e) {
                    loggerHelper.warn("TestCaseClassLoader.jarOpenFail", jarFile, ",異常:", e);
                }
            }
        }
    }

    private Object getClassLoadingLockInternal(String className) {
        if (GET_CLASSLOADING_LOCK_METHOD != null) {
            try {
                return GET_CLASSLOADING_LOCK_METHOD.invoke(this, className);
            } catch (Exception var3) {
            }
        }

        return this;
    }

    /**
     * 未沒在jar包中的class文件提供加載方法,加載testcase class文件
     *
     * @param path case文件存在的目錄
     * @param name case文件名稱,不包含.class
     * @return
     */
    public Class<?> loadTestCaseClass(String path, String name) {
        if (path == null || name == null) {
            return null;
        }
        //不能以.class結尾
        if (name.endsWith(".class")) {
            name = name.substring(0, name.length() - 6);
        }
        // 讀取文件內容
        File caseFile = new File(path, name + ".class");
        // 文件不存在,直接返回null
        if (caseFile.exists()) {
            int contentLength = (int) caseFile.length();
            byte[] buf = new byte[contentLength];
            int pos = 0;

            try {
                InputStream binaryStream = new FileInputStream(caseFile);
                while (true) {
                    int n = binaryStream.read(buf, pos, buf.length - pos);
                    if (n <= 0) {
                        break;
                    }
                    pos += n;
                }
            } catch (IOException var96) {
                loggerHelper.error("TestCaseClassLoader.readError", name, var96);
                return null;
            }
            // name中不能包含.class
            // 字節碼轉爲name名稱的class對象
            Class clazz = this.defineClass(name, buf, 0, contentLength);
            return clazz;
        }
        return null;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (this.getClassLoadingLockInternal(name)) {
            if (loggerHelper.isDebugEnabled()) {
                loggerHelper.debug("loadClass(" + name + ", " + resolve + ")");
            }

            Class<?> clazz = null;

            // Check our previously loaded local class cache
            clazz = this.findLoadedClass0(name);
            if (clazz != null) {
                if (loggerHelper.isDebugEnabled()) {
                    loggerHelper.debug("  Returning class from cache");
                }

                if (resolve) {
                    this.resolveClass(clazz);
                }

                return clazz;
            }

            //  Check our previously loaded class cache
            clazz = this.findLoadedClass(name);
            if (clazz != null) {
                if (loggerHelper.isDebugEnabled()) {
                    loggerHelper.debug("  Returning class from cache");
                }

                if (resolve) {
                    this.resolveClass(clazz);
                }

                return clazz;
            }

            //  Try loading the class with the system class loader, to prevent
            //       the webapp from overriding J2SE classes
            try {
                clazz = this.j2seClassLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve) {
                        this.resolveClass(clazz);
                    }

                    return clazz;
                }
            } catch (ClassNotFoundException var12) {
            }


            //  Search local repositories
            if (loggerHelper.isDebugEnabled()) {
                loggerHelper.debug("  Searching local repositories");
            }
            try {
                clazz = this.findClass(name);
                if (clazz != null) {
                    if (loggerHelper.isDebugEnabled()) {
                        loggerHelper.debug("  Loading class from local repository");
                    }

                    if (resolve) {
                        this.resolveClass(clazz);
                    }

                    return clazz;
                }
            } catch (ClassNotFoundException var14) {
            }


            // Delegate to parent unconditionally
            if (loggerHelper.isDebugEnabled()) {
                loggerHelper.debug("  Delegating to parent classloader at end: " + this.parent);
            }
            try {
                clazz = Class.forName(name, false, this.parent);
                if (clazz != null) {
                    if (loggerHelper.isDebugEnabled()) {
                        loggerHelper.debug("  Loading class from parent");
                    }

                    if (resolve) {
                        this.resolveClass(clazz);
                    }

                    return clazz;
                }
            } catch (ClassNotFoundException var10) {
            }
        }

        throw new ClassNotFoundException(name);
    }

    private Class<?> findLoadedClass0(String name) {
        String path = this.binaryNameToPath(name);
        ResourceEntry entry = (ResourceEntry) this.resourceEntries.get(path);
        return entry != null ? entry.loadedClass : null;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (loggerHelper.isDebugEnabled()) {
            loggerHelper.debug("    findClass(" + name + ")");
        }
        Class<?> clazz = null;

        if (!this.validate(name)) {
            throw new ClassNotFoundException(name);
        } else {
            String path = this.binaryNameToPath(name);
            // 查找對應的資源
            ResourceEntry entry = this.findResourceInternal(name, path);

            if (entry == null) {
                throw new ClassNotFoundException(name);
            } else {
                clazz = entry.loadedClass;
                if (clazz == null) {
                    synchronized (this.getClassLoadingLockInternal(name)) {
                        clazz = entry.loadedClass;
                        if (clazz != null) {
                            return clazz;
                        } else if (entry.binaryContent == null) {
                            throw new ClassNotFoundException(name);
                        } else {
                            try {
                                // 字節碼轉爲name名稱的class對象
                                clazz = this.defineClass(name, entry.binaryContent, 0, entry.binaryContent.length, new CodeSource(entry.codeBase, entry.certificates));
                            } catch (UnsupportedClassVersionError var11) {
                                throw new UnsupportedClassVersionError(name);
                            }

                            entry.loadedClass = clazz;
                            entry.binaryContent = null;
                            entry.certificates = null;
                        }
                    }
                }
            }
        }
        if (clazz == null) {
            if (loggerHelper.isDebugEnabled()) {
                loggerHelper.debug("    --> Returning ClassNotFoundException");
            }
            throw new ClassNotFoundException(name);
        }

        if (loggerHelper.isDebugEnabled()) {
            loggerHelper.debug("      Returning class " + clazz);
            ClassLoader cl = clazz.getClassLoader();
            loggerHelper.debug("      Loaded by " + cl.toString());
        }

        return clazz;
    }

    private URL getURI(File file) throws MalformedURLException {
        File realFile = file;

        try {
            realFile = realFile.getCanonicalFile();
        } catch (IOException var4) {
        }

        return realFile.toURI().toURL();
    }

    private ResourceEntry findResourceInternal(File file, String path) {
        ResourceEntry entry = new ResourceEntry();

        try {
            entry.source = this.getURI(new File(file, path));

            return entry;
        } catch (MalformedURLException var5) {
            return null;
        }
    }

    private ResourceEntry findResourceInternal(String name, String path) {
        if (name != null && path != null) {
            JarEntry jarEntry = null;
            String jarEntryPath = path.substring(1);

            // path對應的資源是否已經加載
            ResourceEntry entry = (ResourceEntry) this.resourceEntries.get(path);
            // 不存在,就去查找
            if (entry == null) {
                int contentLength = -1;
                InputStream binaryStream = null;
                int jarFilesLength = this.jarFiles.size();

                // name之前就沒有查找到,直接返回null
                if (this.notFoundResources.containsKey(name)) {
                    return null;
                } else {
                    if (jarFiles.size() > 0) {
                        synchronized (this.jarFiles) {
                            try {
                                // 所有jar包都檢查一遍,過程中如果找到就退出遍歷
                                for (int i = 0; entry == null && i < jarFilesLength; ++i) {
                                    JarFile jarFile = this.jarFiles.get(i);
                                    jarEntry = jarFile.getJarEntry(jarEntryPath);
                                    if (jarEntry != null) {
                                        entry = new ResourceEntry();

                                        entry.codeBase = this.getURI(new File(jarFile.getName()));
                                        entry.source = UriUtil.buildJarUrl(entry.codeBase.toString(), jarEntryPath);
                                        contentLength = (int) jarEntry.getSize();
                                        binaryStream = jarFile.getInputStream(jarEntry);
                                    }
                                }

                                // 所有jar包都沒有找到,放到notFoundResources中
                                if (entry == null) {
                                    synchronized (this.notFoundResources) {
                                        this.notFoundResources.put(name, name);
                                    }

                                    return (ResourceEntry) null;
                                }

                                if (binaryStream != null) {
                                    byte[] buf = new byte[contentLength];
                                    int pos = 0;

                                    try {
                                        // 讀取文件
                                        while (true) {
                                            int n = binaryStream.read(buf, pos, buf.length - pos);
                                            if (n <= 0) {
                                                break;
                                            }

                                            pos += n;
                                        }
                                    } catch (IOException var96) {
                                        loggerHelper.error("TestCaseClassLoader.readError", name, var96);
                                        return null;
                                    }

                                    entry.binaryContent = buf;
                                    if (jarEntry != null) {
                                        entry.certificates = jarEntry.getCertificates();
                                    }
                                }
                            } catch (MalformedURLException e) {
                                e.printStackTrace();
                            } catch (IOException e) {
                                e.printStackTrace();
                            } finally {
                                if (binaryStream != null) {
                                    try {
                                        binaryStream.close();
                                    } catch (IOException var85) {
                                    }
                                }

                            }
                        }

                        // 放到緩存中
                        synchronized (this.resourceEntries) {
                            ResourceEntry entry2 = (ResourceEntry) this.resourceEntries.get(path);
                            if (entry2 == null) {
                                this.resourceEntries.put(path, entry);
                            } else {
                                entry = entry2;
                            }

                            return entry;
                        }
                    }
                }
            }
            return entry;
        }
        return null;
    }

    private boolean validate(String name) {
        if (name == null) {
            return false;
        } else if (name.startsWith("java.")) {
            return false;
        } else if (name.startsWith("javax.servlet.jsp.jstl")) {
            return true;
        } else if (name.startsWith("javax.servlet.")) {
            return false;
        } else {
            return !name.startsWith("javax.el");
        }
    }

    private String binaryNameToPath(String binaryName) {
        StringBuilder path = new StringBuilder(7 + binaryName.length());
        path.append('/');
        path.append(binaryName.replace('.', '/'));
        path.append(".class");
        return path.toString();
    }

    /**
     * SCFSerializableScanner.scan方法會調用classloader的getResources獲取指定包名的class文件
     *
     * @param name
     * @return
     * @throws IOException
     */
    public Enumeration<URL> findResources(String name) throws IOException {
        if (loggerHelper.isDebugEnabled()) {
            loggerHelper.debug("    findResources(" + name + ")");
        }

        LinkedHashSet<URL> result = new LinkedHashSet();
        int jarFilesLength = this.jarFiles.size();

        synchronized (this.jarFiles) {
            for (int i = 0; i < jarFilesLength; ++i) {
                JarFile jarFile = this.jarFiles.get(i);
                JarEntry jarEntry = jarFile.getJarEntry(name);
                if (jarEntry != null) {
                    try {
                        String jarFakeUrl = this.getURI(new File(jarFile.getName())).toString();
                        result.add(UriUtil.buildJarUrl(jarFakeUrl, name));
                    } catch (MalformedURLException var10) {
                    }
                }
            }
        }

        return Collections.enumeration(result);
    }
}

ResourceEntry 
import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;

/**
 * @author [email protected]
 * @version 1.0
 * @date 2020/2/9 20:02
 */
public class ResourceEntry {
    public byte[] binaryContent = null;
    public volatile Class<?> loadedClass = null;
    public URL source = null;
    public URL codeBase = null;
    public Certificate[] certificates = null;

    public ResourceEntry() {
    }
}
UriUtil


import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Pattern;

/**
 * @author [email protected]
 * @version 1.0
 * @date 2020/2/10 17:27
 */
public final class UriUtil {
    private static Pattern PATTERN_EXCLAMATION_MARK = Pattern.compile("!/");
    private static Pattern PATTERN_CARET = Pattern.compile("\\^/");
    private static Pattern PATTERN_ASTERISK = Pattern.compile("\\*/");

    private UriUtil() {
    }

    private static boolean isSchemeChar(char c) {
        return Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.';
    }

    public static boolean hasScheme(CharSequence uri) {
        int len = uri.length();

        for (int i = 0; i < len; ++i) {
            char c = uri.charAt(i);
            if (c == ':') {
                return i > 0;
            }

            if (!isSchemeChar(c)) {
                return false;
            }
        }

        return false;
    }

    public static URL buildJarUrl(File jarFile) throws MalformedURLException {
        return buildJarUrl((File) jarFile, (String) null);
    }

    public static URL buildJarUrl(File jarFile, String entryPath) throws MalformedURLException {
        return buildJarUrl(jarFile.toURI().toString(), entryPath);
    }

    public static URL buildJarUrl(String fileUrlString) throws MalformedURLException {
        return buildJarUrl((String) fileUrlString, (String) null);
    }

    public static URL buildJarUrl(String fileUrlString, String entryPath) throws MalformedURLException {
        String safeString = makeSafeForJarUrl(fileUrlString);
        StringBuilder sb = new StringBuilder();
        sb.append(safeString);
        sb.append("!/");
        if (entryPath != null) {
            sb.append(makeSafeForJarUrl(entryPath));
        }

        return new URL("jar", (String) null, -1, sb.toString());
    }

    public static URL buildJarSafeUrl(File file) throws MalformedURLException {
        String safe = makeSafeForJarUrl(file.toURI().toString());
        return new URL(safe);
    }

    private static String makeSafeForJarUrl(String input) {
        String tmp = PATTERN_EXCLAMATION_MARK.matcher(input).replaceAll("%21/");
        tmp = PATTERN_CARET.matcher(tmp).replaceAll("%5e/");
        return PATTERN_ASTERISK.matcher(tmp).replaceAll("%2a/");
    }
}

參考

深入理解 Tomcat(四)Tomcat 類加載器之爲何違背雙親委派模型
Tomcat源碼研究之ClassLoader
違反ClassLoader雙親委派機制三部曲第二部——Tomcat類加載機制
Java中自定義ClassLoader和ClassLoader的使用
Tomcat類加載器以及應用間class隔離與共享

發佈了50 篇原創文章 · 獲贊 4 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章