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;
}
// 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(),打開資源的連接,爲接下來資源的訪問作好準備。