在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進階的文章,感謝關注