• 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

    知识碎片,重在整理,路很长,一步一个脚印,就好。
  • 相关阅读:
    洛谷 P3366 【模板】最小生成树
    洛谷 P2820 局域网
    一本通【例4-10】最优布线问题
    洛谷 P1546 最短网络 Agri-Net
    图论模板
    洛谷 AT667 【天下一人力比較】
    刷题记录
    洛谷P1553 数字翻转(升级版)
    tornado硬件管理系统-网络与磁盘的实现(7)
    tornado硬件管理系统-内存与swap的实现(6)
  • 原文地址:https://www.cnblogs.com/lmk-sym/p/6559975.html
Copyright © 2020-2023  润新知