在Java
中,爲了從相對路徑讀取文件,常常會使用的方法即是:html
xxx.class.getResource();
xxx.class.getClassLoader().getResource();
複製代碼
在Spring
中,咱們還能夠經過Spring
提供的Resource
進行一些操做:java
ClassPathResource
FileSystemResource
ServletContextResource
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
複製代碼
這裏簡單總結下他們的區別:web
這個方法是今天的主角。spring
咱們都知道ClassLoader
的做用是用來加載.class
文件的,而且ClassLoader
是遵循Java
類加載中的雙親委派機制的。api
那麼,ClassLoader
是如何找到這個.class
文件的呢?答案是URLClassPath
微信
Java
中自帶了3個ClassLoader
分別是BootStrap ClassLoader
,EtxClassLoader
,AppClassLoader
,app
這3個ClassLoader
都繼承自URLClassLoader
,而URLClassLoader
中包含一個URLClassPath
用來記錄每一個ClassLoader
對應的加載.class
文件的路徑,當須要加載資源的時候,只管從URLClassPath
對應的路徑查找便可。webapp
下面是測試代碼:ide
System.out.println("BootStrap ClassLoader ");
Stream.of(System.getProperty("sun.boot.class.path").split(";")).forEach(System.out::println);
System.out.println("ExtClassLoader:");
Stream.of(System.getProperty("java.ext.dirs").split(";")).forEach(System.out::println);
System.out.println("AppClassLoader:");
Stream.of(System.getProperty("java.class.path").split(";")).forEach(System.out::println);
複製代碼
輸出以下:測試
BootStrap ClassLoader
H:\java\jdk1.8\jre\lib\resources.jar
H:\java\jdk1.8\jre\lib\rt.jar
H:\java\jdk1.8\jre\lib\sunrsasign.jar
H:\java\jdk1.8\jre\lib\jsse.jar
H:\java\jdk1.8\jre\lib\jce.jar
H:\java\jdk1.8\jre\lib\charsets.jar
H:\java\jdk1.8\jre\lib\jfr.jar
H:\java\jdk1.8\jre\classes
ExtClassLoader:
H:\java\jdk1.8\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
AppClassLoader:
H:\java\jdk1.8\jre\lib\charsets.jar
H:\java\jdk1.8\jre\lib\deploy.jar
H:\java\jdk1.8\jre\lib\ext\access-bridge-64.jar
H:\java\jdk1.8\jre\lib\ext\cldrdata.jar
H:\java\jdk1.8\jre\lib\ext\dnsns.jar
H:\java\jdk1.8\jre\lib\ext\jaccess.jar
H:\java\jdk1.8\jre\lib\ext\jfxrt.jar
H:\java\jdk1.8\jre\lib\ext\localedata.jar
H:\java\jdk1.8\jre\lib\ext\nashorn.jar
H:\java\jdk1.8\jre\lib\ext\sunec.jar
H:\java\jdk1.8\jre\lib\ext\sunjce_provider.jar
H:\java\jdk1.8\jre\lib\ext\sunmscapi.jar
H:\java\jdk1.8\jre\lib\ext\sunpkcs11.jar
H:\java\jdk1.8\jre\lib\ext\zipfs.jar
H:\java\jdk1.8\jre\lib\javaws.jar
H:\java\jdk1.8\jre\lib\jce.jar
H:\java\jdk1.8\jre\lib\jfr.jar
H:\java\jdk1.8\jre\lib\jfxswt.jar
H:\java\jdk1.8\jre\lib\jsse.jar
H:\java\jdk1.8\jre\lib\management-agent.jar
H:\java\jdk1.8\jre\lib\plugin.jar
H:\java\jdk1.8\jre\lib\resources.jar
H:\java\jdk1.8\jre\lib\rt.jar
F:\spring-test\target\classes
複製代碼
AppClassLoader
負責經常使用的JDK jar
以及項目所依賴的jar
包上述參數能夠經過 sun.misc.Launcher.class得到
經過輸出的參數,咱們能夠清晰的看出來各個
ClassLoader
負責的區域
說了這麼多,這個和ClassLoader#getResource()
有什麼關係呢?
關係很大,前面剛剛提問過,ClassLoader
是如何讀取.class
文件的呢?
答案是URLClassPath#getResource()
方法:每一個UrlClassLoader
都是經過URLClassPath
來存儲對應的加載區域,當須要查找.class
文件的時候,就經過URLClassPath#getResource()
查找便可。
下面再來看看ClassLoader#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;
}
//因爲BootStrap ClassLoader是C++寫的,Java拿不到其引用。
//所以這裏單獨寫了一個方法獲取BootStrapResource()
private static URL getBootstrapResource(String name) {
URLClassPath ucp = getBootstrapClassPath();
Resource res = ucp.getResource(name);
return res != null ? res.getURL() : null;
}
複製代碼
URLClassLoader#findResource()
public URL findResource(final String name) {
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
複製代碼
咱們只用注意這一句ucp.findResource(name, true);
,這邊是查找.class
文件的方法,所以咱們能夠總結出經過ClassLoader#getResource()
的流程:
AppClassLoader
委派給ExtClassLoader
查找是否存在對應的資源ExtClassLoader
委派給BootStrap ClassLoader
查找是有存在對應的資源BootStrap ClassLoader
經過URLClasspath
查找本身加載的區域,查找到了即返回BootStrap ClassLoader
未查找到對應資源,ExtClassLoader
經過URLClasspath
查找本身加載的區域,查找到了即返回ExtClassLoader
未查找到對應資源,AppClassLoader
經過URLClasspath
查找本身加載的區域,查找到了即返回AppClassLoader
未查找到,拋出異常。這個過程,就和加載.class
文件的過程同樣。
在這裏咱們就能夠發現,經過ClassLoader#getResource()
能夠獲取JDK
資源,所依賴的JAR
包資源等
所以,咱們甚至能夠這樣寫:
//讀取java.lang.String.class
的字節碼
InputStream inputStream =Test.class.getClassLoader().getResourceAsStream("java/lang/String.class");
try(BufferedInputStream bufferedInputStream=new BufferedInputStream(inputStream)){
byte[] bytes=new byte[1024];
while (bufferedInputStream.read(bytes)>0){
System.out.println(new String(bytes, StandardCharsets.UTF_8));
}
}
複製代碼
明白了ClassLoader#getResource()
,其實本篇文章就差很少了,由於後面要將的幾個方法,底層都是ClassLoader#getResource()
class##getResource()
底層就是ClassLoader#getResource()
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
複製代碼
不過有個小區別就在於class#getResource()
多了一個resolveName()
方法:
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (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;
}
複製代碼
這個resolveName()
大體就是判斷路徑是相對路徑仍是絕對路徑,若是是相對路徑,則資源名會被加上當前項目的根路徑:
Test.class.getResource("spring-config.xml");
複製代碼
resolve以後變成
com/dengchengchao/test/spring-config.xml
複製代碼
這樣的資源就只能在當前項目中找到。
Test.class.getResource("test.txt"); //相對路徑
Test.class.getResource("/"); //根路徑
複製代碼
注意:
ClassLoader#getResource()
不能以/
開頭
在Spring
中,對Resource
進行了擴展,使得Resource
可以適應更多的應用場景,
不過ClssPathResource()
底層依然是ClassLoader##getResource()
,所以ClassLoader##getResource()
d的特性,ClassPathResource
也支持。
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
} else {
return this.classLoader != null ? this.classLoader.getResource(this.path) : ClassLoader.getSystemResource(this.path);
}
}
複製代碼
ClassPathResource
用於讀取classes
目錄文件
通常來講,對於SpringBoot
項目,打包後的項目結構以下:
xxx.jar
|--- BOOT-INF
|--------|--classes
|--------|----|--com
|--------|----|-- application.properties
|--------|----|--logback.xml
| -------|-- lib
|--- META-INF
|--- org
能夠看到,ClassPathResource()
的起始路徑即是classes
,平時咱們讀取的application.properties
即是使用ClasspathResource()
獲取的
在平時使用的過程當中,有三點須要注意:
classpath 和 classpath* 區別:
classpath:只會返回第一個查找到的文件 classpath*:會返回全部查找到的文件
在Spring
中,須要直接表示使用ClassPathResource()
來查找的話,能夠直接添加classpath:
頭
使用classpath
以/
和不以/
開頭沒有區別
ServletContextResource
是針對Servlet
來作的,咱們知道,Servlet
規定webapp
目錄以下:
而ServletContextResource
的路徑則是xxx
目錄下爲起點。也就是能夠經過ServletContextResource
獲取到form.html
等資源。
同時對比上面的ClassPathResource
咱們能夠發現:
"classpath:com"
複製代碼
等價於:
ServletContextResource("WEB-INF/classes/com")
複製代碼
FileSystemResource
沒什麼好說的,就是系統目錄資源,好比
ApplicationContext ctx =
new FileSystemXmlApplicationContext("D://test.xml");
複製代碼
它的標記頭爲file:
例如:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("flie:D://test.xml");
複製代碼
若是以爲寫得不錯,歡迎關注微信公衆號:逸遊Java ,天天不定時發佈一些有關Java進階的文章,感謝關注