Class.getResource和ClassLoader.getResource
一案例驅動
二源碼分析
三類加載器ClassLoader
四總結
五參考
一案例驅動
最近加載文件的時候遇到了一個問題,很有意思! 具體看下面案例代碼
file:/C:/myroad/utalitityUtils/target/classes/com/zsk/java/
file:/C:/myroad/utalitityUtils/target/classes/
file:/C:/myroad/utalitityUtils/target/classes/
null
那這兩種方式有什么區別呢?下面跟源碼一探究竟。
二源碼分析
首先我們跟一下這段源碼:
下面是ClassLoader的getResource方法
public URL getResource(String name) {URL url;if (parent != null) {//這里的parent為sun.misc.Launcher$ExtClassLoader@7d4793a8url = parent.getResource(name);//這里是一個遞歸調用,再次進入之后parent為null} else {url = getBootstrapResource(name);//到達系統啟動類加載器}if (url == null) {//系統啟動類加載器沒有加載到,遞歸回退到第一次調用然后是擴展類加載器url = findResource(name);}return url;//最后如果都沒有加載到,雙親委派加載失敗,則加載應用本身自己的加載器。}關于上面的
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7d4793a8
后面原理會詳細介紹,源碼沒看明白,也許看了下面的原理 就豁然開朗了。
下面我們跟一下getClassLoader源碼看一下調用過程:
System.out.println(TestClassLoader.class.getClassLoader().getResource("")); 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;}可以發現其實,Class.getResource和ClassLoader.getResource 最終調用的是ClassLoader 類的getResource方法。只不過Class.getResource是先調用Class 的 getResource 方法,在這個getResource 方法中,再去調用ClassLoader 類的getResource方法
那么Class類中的getResource方法做了什么呢,主要的一句是 name = resolveName(name);
我們看一下這個代碼實現:
private String resolveName(String name) {if (name == null) {return name;}if (!name.startsWith("/")) { //對于不以/開頭的文件,Class<?> c = this; //獲取當前加載類的完整的類路徑,我這里是com.zsk.java.TestClassLoaderwhile (c.isArray()) {c = c.getComponentType();}String baseName = c.getName();int index = baseName.lastIndexOf('.');//找到文件的包名稱if (index != -1) {name = baseName.substring(0, index).replace('.', '/')+"/"+name;//將包名稱中的.替換為/ 并在最后加上/ 文件名}} else {name = name.substring(1); //對于/開頭的文件名,會只保留文件名稱部分。}return name;}TestClassLoader.class.getResource("")
Class類中的getResource方法返回的是com/zsk/java/
ClassLoader類中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/com/zsk/java/
TestClassLoader.class.getResource("/")
Class類中的getResource方法返回的是""
ClassLoader類中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/
這也就解釋了,為什么我們放在resource文件夾中的文件,第一個返回null ,而第二個可以正常訪問了。
(JDK設置這樣的規則,是很好理解的,path不以’/‘開頭時,我們就能獲取與當前類所在的路徑相同的資源文件,而以’/'開頭時可以獲取ClassPath根下任意路徑的資源。)
TestClassLoader.class.getClassLoader().getResource("")
ClassLoader類中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/
TestClassLoader.class.getClassLoader().getResource("/")
ClassLoader類中的getResource方法返回的是 null
對于ClassLoader.getResource, 直接調用的就是ClassLoader 類的getResource方法,那么對于getResource(""),path不以’/‘開頭時,首先通過雙親委派機制,使用的逐級向上委托的形式加載的,最后發現雙親沒有加載到文件,最后通過當前類加載classpath根下資源文件。對于getResource("/"),’/'表示Boot ClassLoader中的加載范圍,因為這個類加載器是C++實現的,所以加載范圍為null。
三類加載器ClassLoader
1、類加載器(ClassLoader)
我們都知道 Java 文件被運行,第一步,需要通過 javac 編譯器編譯為 class 文件;第二步,JVM 運行 class 文件,實現跨平臺。而 JVM 虛擬機第一步肯定是 加載 class 文件,所以,類加載器實現的就是(來自《深入理解Java虛擬機》):
通過一個類的全限定名來獲取描述此類的二進制字節流
類加載器有幾個重要的特性:
每個類加載器都有自己的預定義的搜索范圍,用來加載 class 文件;
每個類和加載它的類加載器共同確定了這個類的唯一性,也就是說如果一個 class 文件被不同的類加載器加載到了 JVM 中,那么這兩個類就是不同的類,雖然他們都來自同一份 class 文件;
雙親委派模型。
2.1 雙親委派模型
所有的類加載器都是有層級結構的,每個類加載器都有一個父類類加載器(通過組合實現,而不是繼承),除了啟動類加載器(Bootstrap ClassLoader);
當一個類加載器接收到一個類加載請求時,首先將這個請求委派給它的父加載器去加載,所以每個類加載請求最終都會傳遞到頂層的啟動類加載器,如果父加載器無法加載時,子類加載器才會去嘗試自己去加載;
通過雙親委派模型就實現了類加載器的三個特性:
委派(delegation):子類加載器委派給父類加載器加載;
可見性(visibility):子類加載器可訪問父類加載器加載的類,父類不能訪問子類加載器加載的類;
唯一性(uniqueness):可保證每個類只被加載一次,比如 Object 類是被 Bootstrap ClassLoader 加載的,因為有了雙親委派模型,所有的 Object 類加載請求都委派到了 Bootstrap ClassLoader,所以保證了只被加載一次。
以上就是類加載器的一些特性,那么在 Java 中類加載器是如何實現的呢?
2.2 Java 中的類加載器
從 JVM 虛擬機的角度來看,只存在兩種不同的類加載器:
啟動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分;
所有其他的類加載器,獨立于虛擬機外部,都繼承自抽象類 java.lang.ClassLoader。
而絕大多數 Java 應用都會用到如下 3 中系統提供的類加載器:
啟動類加載器(Bootstrap/Primordial/NULL ClassLoader):頂層的類加載器,沒有父類加載器。負責加載 /lib 目錄下的,或則被 -Xbootclasspath 參數所指定路徑中的,并被 JVM 識別的(僅按文件名識別,如 rt.jar,名字不符合的類庫即使放在 lib 目錄也不會被加載)類庫加載到虛擬機內存中。所有被 Bootstrap classloader 加載的類,它的 Class.getClassLoader 方法返回的都是 null,所以也稱作 NULL ClassLoader。
擴展類加載器(Extension CLassLoader):由 sun.misc.LauncherExtClassLoader實現,負責加載<JAVAHOME>/lib/ext目錄下,或被java.ext.dirs系統變量所指定的目錄下的所有類庫;應用程序類加載器(Application/SystemClassLoader):由sun.misc.LauncherExtClassLoader 實現,負責加載 <JAVA_HOME>/lib/ext 目錄下,或被 java.ext.dirs 系統變量所指定的目錄下的所有類庫; 應用程序類加載器(Application/System ClassLoader):由 sun.misc.LauncherExtClassLoader實現,負責加載<JAVAH?OME>/lib/ext目錄下,或被java.ext.dirs系統變量所指定的目錄下的所有類庫;應用程序類加載器(Application/SystemClassLoader):由sun.misc.LauncherAppClassLoader 實現。它是 ClassLoader.getSystemClassLoader() 方法的默認返回值,所以也稱為系統類加載器(System ClassLoader)。它負責加載 classpath 下所指定的類庫,如果應用程序沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
如下,就是 Java 程序中的類加載器層級結構圖:
以上,我們介紹了 Java 系統的類加載器,
四總結
最后進行一個總結,Class.getResource和ClassLoader.getResource的區別,就是在加載資源文件的時候,加載方式的不同,
class.getResource("/") == class.getClassLoader().getResource("")
其實,Class.getResource和ClassLoader.getResource本質上是一樣的,都是使用ClassLoader.getResource加載資源的。
Class.getResource真正調用ClassLoader.getResource方法之前,會先獲取文件的路徑(path不以’/‘開頭時,默認是從此類所在的包下取資源;path以’/'開頭時,則是從項目的ClassPath根下獲取資源)。
ClassLoader.getResource方法會通過雙親委派機制,先委派雙親去加載類,如果雙親沒有加載到,則再由自己加載。
總結
以上是生活随笔為你收集整理的Class.getResource和ClassLoader.getResource的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Snap svg 主要对象
- 下一篇: 昆仑通态触摸屏如何把参数由触摸屏传递到P