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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM