spring IOC源碼學習(二):BeanDefinition資源加載

spring IOC資源加載,資源的查找定位,類的繼承結構如下:


資源的加載是通過類XmlBeanDefinitionReader類來實現的。經過之前的一系列調用 ,到達loadBeanDefinitions方法。如下,非關鍵代碼已經去掉。

	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

執行 InputStream inputStream =encodedResource.getResource().getInputStream() 獲得資源的I/O。由上圖中類的繼承關係,接口InputStreamStream的方法getInputStream()。接口方法的實現類爲ClassPathResource。方法的實現如下:

	public InputStream getInputStream() throws IOException {
		InputStream is;
		if (this.clazz != null) {
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		if (is == null) {
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		return is;
	}


spring IOC中使用的類加載器是“應用類加載器”,在初始化時已經確定,因此classLoader不爲null。進入第二個判斷入口。調用抽象類ClassLoader的getResourceAsStream()方法,方法的實現類是java.net.URLClassLoader,返回資源的I/O,如下:

    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(jar, null);
                    }
                }
            } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
                synchronized (closeables) {
                    closeables.put(is, null);
                }
            }
            return is;
        } catch (IOException e) {
            return null;
        }
    }

ClassLoader類的getResource、getResources等方法可以加載classpath中的資源,ClassLoader獲取資源傳入的參數是相當於classpath的相對路徑,如果某個資源想要被ClassLoader加載。該資源需要放到當前的classpath中,或者把資源的目錄、jar包文件作爲classpath。ClassLoader在加載一個資源時默認使用雙親委派模型原則。如果可以通過父加載器找到資源,則自己不必繼續查找,交由父加載器去查找,並返回父加載器查找到的資源。


調用java.lang.ClassLoader的getResource返回資源的i/o,可以看到雙親委派模型在資源查找中的運用。

    public URL getResource(String name) {
        URL url;
        if (parent != null) {
			//擴展類加載器加載資源
            url = parent.getResource(name);
        } else {
			//啓動類加載器加載資源
            url = getBootstrapResource(name);
        }
        if (url == null) {
			//應用類加載器加載資源
            url = findResource(name);
        }
        return url;
    }
因資源文件放在classpath路徑下,默認由應用類加載器加載。即調用url= findResource(name)返回資源信息。


接下來,瞭解getBootstrapResource(name)和findResource(name)。先看getBootstrapResource(name)的執行代碼。

    /**
     * Find resources from the VM's built-in classloader.(通過虛擬機內置的類加載器查找資源。)
     */
    private static URL getBootstrapResource(String name) {
		//取得啓動類加載器的路徑實例
        URLClassPath ucp = getBootstrapClassPath();
		//通過啓動類加載器獲取資源
        Resource res = ucp.getResource(name);
		//返回資源的URL實例對象。
        return res != null ? res.getURL() : null;
    }


getBootstrapClassPath()返回查找系統資源的URLClassPath類對象(啓動類加載器),URLClassPath是jvm的底層代碼,感興趣的可以進去看下。

    // Returns the URLClassPath that is used for finding system resources.
    static URLClassPath getBootstrapClassPath() {
        return sun.misc.Launcher.getBootstrapClassPath();
    }
調試代碼,getBootstrapResource返回值爲null,父類加載器查找的路徑中沒有指定資源,通知子類去加載器類。


執行url= findResource(name);

    /**
     * Finds the resource with the specified name on the URL search path.
     *
     * @param name the name of the resource
     * @return a {@code URL} for the resource, or {@code null}
     * if the resource could not be found, or if the loader is closed.
     */
    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(name, true);
                }
            }, acc);

        return url != null ? ucp.checkURL(url) : null;
    }
其中,AccessController是後續要研究的內容,這裏暫不提及。調試,返回的url不爲null。通過對上述調用過程及結果分析,資源的查找過程符合雙親委派模型原則。

補充一

虛擬機類加載過程的“加載”階段。在加載階段,虛擬機需要完成以下三件事情:

1、 通過一個類的全限定名獲取定義此類的二進制字節流。

2、 將這個字節流代表的靜態存儲結構轉化爲方法區運行時數據結構。

3、 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據訪問入口。

虛擬機規範的這3點要求,其實並不算具體。如第一點,通過一個類的全限定名獲取定義此類的二進制字節流。它沒有指明二進制字節流要從一個class文件獲取,也沒有說怎樣獲取,靈活度相當大。例如:

1、 從zip包中獲取。

2、 從網絡獲取。

3、 運行時計算生成,使用最多的場景就是動態代理技術。

4、 由其它文件生成。

5、 從數據庫中讀取。


補充二

URL類的用法。java URL處理,URL(UniformResource Loacator)統一資源定位符,表示爲互聯網上的資源。java網絡類可以通過網絡或者遠程連接來實現應用。而且,可以對國際互聯網以及URL資源進行訪問。java的URL類可以讓訪問網絡資源就像是訪問本地文件夾一樣方便快捷。可以通過使用java的URL類經由URL完成讀取和修改數據操作。看個例子:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class UrlTest {
	public static void main(String[] args) throws IOException {
		
		//創建url連接實例
		URL url = new URL("https://www.baidu.com");
		System.out.println(url);
		//在訪問這個連接的資源和內容之前,需要打開這些資源和內容上的連接,使用openConnection來完成這一操作
		URLConnection urlcon = (URLConnection)url.openConnection();
		InputStream is = urlcon.getInputStream();
		BufferedReader br = new BufferedReader(new InputStreamReader(is));
		StringBuffer sb = new StringBuffer();
		String s = null;
		while((s=br.readLine())!=null){
			sb.append(s).append("\n");
		}
		System.out.println(sb.toString());
	}
}
通過上述例子,對URL的使用有了直觀的瞭解。spring中獲取到資源的URL,執行 URLConnection urlc = url.openConnection(),打開資源的連接,爲接下來資源的訪問作好準備。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章