LaunchedURLClassLoade

public class Handler extends URLStreamHandler {
    private static final String SEPARATOR = "!/";
    private static SoftReference<Map<File, JarFile>> rootFileCache;
    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        if (this.jarFile != null) {
            return new JarURLConnection(url, this.jarFile);
        }
        try {
            return new JarURLConnection(url, getRootJarFileFromUrl(url));
        }
        catch (Exception ex) {
            return openFallbackConnection(url, ex);
        }
    }
    public JarFile getRootJarFileFromUrl(URL url) throws IOException {
        String spec = url.getFile();
        int separatorIndex = spec.indexOf(SEPARATOR);
        if (separatorIndex == -1) {
            throw new MalformedURLException("Jar URL does not contain !/ separator");
        }
        String name = spec.substring(0, separatorIndex);
        return getRootJarFile(name);
    }

ClassLoader如何读取到Resource

对于一个ClassLoader,它需要哪些能力?

  • 查找资源
  • 读取资源

对应的API是:

public URL findResource(String name)
public InputStream getResourceAsStream(String name)
  • 1
  • 2

上面提到,Spring boot构造LaunchedURLClassLoader时,传递了一个URL[]数组。数组里是lib目录下面的jar的URL。

对于一个URL,JDK或者ClassLoader如何知道怎么读取到里面的内容的?

实际上流程是这样子的:

  • LaunchedURLClassLoader.loadClass
  • URL.getContent()
  • URL.openConnection()
  • Handler.openConnection(URL)

最终调用的是JarURLConnection的getInputStream()函数。

//org.springframework.boot.loader.jar.JarURLConnection
    @Override
    public InputStream getInputStream() throws IOException {
        connect();
        if (this.jarEntryName.isEmpty()) {
            throw new IOException("no entry name specified");
        }
        return this.jarEntryData.getInputStream();
    }
  • 1

从一个URL,到最终读取到URL里的内容,整个过程是比较复杂的,总结下:

  • spring boot注册了一个Handler来处理”jar:”这种协议的URL
  • spring boot扩展了JarFile和JarURLConnection,内部处理jar in jar的情况
  • 在处理多重jar in jar的URL时,spring boot会循环处理,并缓存已经加载到的JarFile
  • 对于多重jar in jar,实际上是解压到了临时目录来处理,可以参考JarFileArchive里的代码
  • 在获取URL的InputStream时,最终获取到的是JarFile里的JarEntryData

这里面的细节很多,只列出比较重要的一些点。

然后,URLClassLoader是如何getResource的呢?

URLClassLoader在构造时,有URL[]数组参数,它内部会用这个数组来构造一个URLClassPath:

URLClassPath ucp = new URLClassPath(urls);
  • 1

在 URLClassPath 内部会为这些URLS 都构造一个Loader,然后在getResource时,会从这些Loader里一个个去尝试获取。 
如果获取成功的话,就像下面那样包装为一个Resource。

Resource getResource(final String name, boolean check) {
    final URL url;
    try {
        url = new URL(base, ParseUtil.encodePath(name, false));
    } catch (MalformedURLException e) {
        throw new IllegalArgumentException("name");
    }
    final URLConnection uc;
    try {
        if (check) {
            URLClassPath.check(url);
        }
        uc = url.openConnection();
        InputStream in = uc.getInputStream();
        if (uc instanceof JarURLConnection) {
            /* Need to remember the jar file so it can be closed
             * in a hurry.
             */
            JarURLConnection juc = (JarURLConnection)uc;
            jarfile = JarLoader.checkJar(juc.getJarFile());
        }
    } catch (Exception e) {
        return null;
    }
    return new Resource() {
        public String getName() { return name; }
        public URL getURL() { return url; }
        public URL getCodeSourceURL() { return base; }
        public InputStream getInputStream() throws IOException {
            return uc.getInputStream();
        }
        public int getContentLength() throws IOException {
            return uc.getContentLength();
        }
    };
}
posted @ 2021-10-21 22:03  青竹之下  阅读(1950)  评论(0编辑  收藏  举报