SpringBoot启动里的细节问题

[TOC]
## 简述
> 自己写了一篇Springboot的启动流程,然后我发现还有http://blog.csdn.net/hengyunabc/article/details/50120001 这一篇写的比较好,我就继续的把加载里的细节给描述清楚。
### 如何读取到资源文件?
    我们从上一篇《SpringBoot应用启动流程》这一篇知道了LaunchedURLClassLoader这个类,但是没有具体说。LaunchedURLClassLoader继承了URLClassLoader,然后URLCLassLoader又继承了SecureClassLoader,最后SecureClassLoader继承ClassLoader。LaunchedURLClassLoader其实 就是通过Url的方式来加载类。
LaunchedURLClassLoader被执行前需要静态块里的方法,这是使用java7的并行加载机制,所以要在静态方法里将ClassLoader注册为可并行加载。
```
    static {
        performParallelCapableRegistration();
    }

    @UsesJava7
    private static void performParallelCapableRegistration() {
        try {
            ClassLoader.registerAsParallelCapable();
        }
        catch (NoSuchMethodError ex) {
            // Running on Java 6. Continue.
        }
    }
```
我们重新把思路整理一下,SpringBoot在启动的时候构造LaunchedURLClassLoader,这个类要求是执行并行加载,然后LaunchedURLClassLoader构造函数通过拿到的URL数组(该数组就是Lib底下的jar路径),来加载Jar包。上面的继承关系我们也知道了,在LaunchedURLClassLoader重写了URLClassLoader的findResource方法findResources方法,这两个方法,代码如下:
```
        //LaunchedURLClassLoader重写的方法
    @Override
    public URL findResource(String name) {
        Handler.setUseFastConnectionExceptions(true);
        try {
            return super.findResource(name);
        }
        finally {
            Handler.setUseFastConnectionExceptions(false);
        }
    }
    @Override
    public Enumeration<URL> findResources(String namethrows IOException {
        Handler.setUseFastConnectionExceptions(true);
        try {
            return super.findResources(name);
        }
        finally {
            Handler.setUseFastConnectionExceptions(false);
        }
    }
```
从LaunchedURLClassLoader重写查找资源的方法上只是添加的异常处理,具体还是调用的是URLClassLoader查找资源的方法。然后就是读取资源。
```
    public URL findResource(final String name) {
        /*
         * The same restriction to finding classes applies to resources
         */
        URL url = AccessController.doPrivileged(
            new PrivilegedAction<URL>() {
                public URL run() {
                    return ucp.findResource(nametrue);
                }
            }, acc);
        return url != null ? ucp.checkURL(url) : null;
    }
    public Enumeration<URL> findResources(final String name)
        throws IOException
    {
        final Enumeration<URL> e = ucp.findResources(nametrue);
        return new Enumeration<URL>() {
            private URL url = null;
            private boolean next() {
                if (url != null) {
                    return true;
                }
                do {
                    URL u = AccessController.doPrivileged(
                        new PrivilegedAction<URL>() {
                            public URL run() {
                                if (!e.hasMoreElements())
                                    return null;
                                return e.nextElement();
                            }
                        }, acc);
                    if (u == null)
                        break;
                    url = ucp.checkURL(u);
                } while (url == null);
                return url != null;
            }
            public URL nextElement() {
                if (!next()) {
                    throw new NoSuchElementException();
                }
                URL u = url;
                url = null;
                return u;
            }
            public boolean hasMoreElements() {
                return next();
            }
        };
    }
    public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            if (url == null) {
                return null;
            }
            URLConnection urlc = url.openConnection();
            InputStream is = urlc.getInputStream();
            if (urlc instanceof JarURLConnection) {
                JarURLConnection juc = (JarURLConnection)urlc;
                JarFile jar = juc.getJarFile();
                synchronized (closeables) {
                    if (!closeables.containsKey(jar)) {
                        closeables.put(jarnull);
                    }
                }
            } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
                synchronized (closeables) {
                    closeables.put(isnull);
                }
            }
            return is;
        } catch (IOException e) {
            return null;
        }
    }
```
### 资源获取总结
    资源的获取,就是使用的是URLClassLoader的查找资源与读取资源的方法。

### 对于一个URL,JDK或者ClassLoader如何知道怎么读取到里面的内容的?
    实际上流程是这样子的:
   * LaunchedURLClassLoader.loadClass
   * URL.getContent()
   * URL.openConnection()
   * Handler.openConnection(URL)
最终调用的是JarURLConnection的getInputStream()函数。
```
    public InputStream getInputStream() throws IOException {
        if (this.jarFile == null) {
            throw FILE_NOT_FOUND_EXCEPTION;
        }
        if (this.jarEntryName.isEmpty()
                && this.jarFile.getType() == JarFile.JarFileType.DIRECT) {
            throw new IOException("no entry name specified");
        }
        connect();
        InputStream inputStream = (this.jarEntryName.isEmpty()
                ? this.jarFile.getData().getInputStream(ResourceAccess.ONCE)
                : this.jarFile.getInputStream(this.jarEntry));
        if (inputStream == null) {
            throwFileNotFound(this.jarEntryNamethis.jarFile);
        }
        return inputStream;
    }
```
从一个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:
```
    /* The search path for classes and resources */
    private final URLClassPath ucp;
    public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        ucp = new URLClassPath(urls); //构造成一个URLClassPath
        this.acc = AccessController.getContext();
    }
```
在 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(); } }; }
```

从代码里可以看到,实际上是调用了url.openConnection()。这样完整的链条就可以连接起来了。

注意,URLClassPath这个类的代码在JDK里没有自带,在这里看到 http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/misc/URLClassPath.java#506

知识碎片,重在整理,路很长,一步一个脚印,就好。
原文地址:https://www.cnblogs.com/lmk-sym/p/6559975.html