深入瞭解 Java Resource && Spring Resource
在Java
中,為了從相對路徑讀取檔案,經常會使用的方法便是:
xxx.class.getResource();
xxx.class.getClassLoader().getResource();
在Spring
中,我們還可以通過Spring
提供的Resource
進行一些操作:
ClassPathResource
FileSystemResource
ServletContextResource
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
這裡簡單總結下他們的區別:
ClassLoader##getResource()
這個方法是今天的主角。
我們都知道ClassLoader
的作用是用來載入.class
檔案的,並且ClassLoader
是遵循Java
類載入中的雙親委派機制的。
那麼,ClassLoader
是如何找到這個.class
檔案的呢?答案是URLClassPath
Java
中自帶了3個ClassLoader
分別是BootStrap ClassLoader
,EtxClassLoader
,AppClassLoader
,
這3個ClassLoader
都繼承自URLClassLoader
,而URLClassLoader
中包含一個URLClassPath
用來記錄每個ClassLoader
對應的載入.class
檔案的路徑,當需要載入資源的時候,只管從URLClassPath
下面是測試程式碼:
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()
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 # ClassPathResource()
在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
以/
和不以/
開頭沒有區別
Spring # ServletContextResource
ServletContextResource
是針對Servlet
來做的,我們知道,Servlet
規定webapp
目錄如下:
而ServletContextResource
的路徑則是xxx
目錄下為起點。也就是可以通過ServletContextResource
獲取到form.html
等資源。
同時對比上面的ClassPathResource
我們可以發現:
"classpath:com"
等價於:
ServletContextResource("WEB-INF/classes/com")
Spring # FileSystemResource
FileSystemResource
沒什麼好說的,就是系統目錄資源,比如
ApplicationContext ctx =
new FileSystemXmlApplicationContext("D://test.xml");
它的標記頭為file:
例如:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("flie:D://test.xml");
如果覺得寫得不錯,歡迎關注微信公眾號:逸遊Java ,每天不定時釋出一些有關Java進階的文章,感謝關注
相關推薦
深入瞭解 Java Resource && Spring Resource
在Java中,為了從相對路徑讀取檔案,經常會使用的方法便是: xxx.class.getResource(); xxx.class.getClassLoader().getResource(); 在Spring中,我們還可以通過Spring提供的Resource進行一些操作: ClassPathResour
深入理解 Java 中的 try-with-resource
背景 眾所周知,所有被開啟的系統資源,比如流、檔案或者Socket連線等,都需要被開發者手動關閉,否則隨著程式的不斷執行,資源洩露將會累積成重大的生產事故。 在Java的江湖中,存在著一種名為finally的功夫,它可以保證當你習武走火入魔之時,還可以
#Java乾貨分享:一篇文章讓你深入瞭解Java中的包和介面
很多新手程式設計師對於Java中兩個具創新性的特徵————包與介面不是非常清楚,所以我特意發了這篇文章來闡述什麼是包,什麼是介面。 包(package)是多個類的容器,它們用於保持類的名稱空間相互隔離。 如果有想學習java的程式設計師,可來我們的java學習扣qun:79979,2590免
深入瞭解Java虛擬機器之Java虛擬機器棧
與程式計數器(想了解計數器看我上一篇部落格)一樣,Java虛擬機器棧也是執行緒私有的,他的生命週期與執行緒相同,虛擬機器棧描述的是Java方法執行的記憶體模式:每個方法在執行的同時都會建立一個棧幀用於儲存區域性變量表,運算元棧,動態連結,方法出
深入瞭解Java之虛擬機器記憶體
在討論JVM記憶體區域分析之前,先來看一下Java程式具體執行的過程: Java 程式的執行過程:Java 原始碼檔案(.Java檔案)-> Java Compiler(Java編譯器)->Java 位元組碼檔案(.class檔案)->類載
深入瞭解Java字串常量池
java中有幾種不同的常量池,以下的內容是對java中幾種常量池的介紹以及重點研究一下字串常量池。 class常量池 我們寫的每一個Java類被編譯後,就會形成一份class檔案;class檔案中除了包含類的版本、欄位、方法、介面等描述資訊外,還有一項資訊就是常量池(constant poo
Java——深入瞭解Java中的迭代器
Java集合框架的集合類,我們有時候稱之為容器。容器的種類有很多種,比如ArrayList、LinkedList、HashSet...,每種容器都有自己的特點,ArrayList底層維護的是一個數組;LinkedList是連結串列結構的;HashSet依賴的是雜湊表,每種容器都有自己特有的資料結構。
深入瞭解 Java Timer 定時任務排程器實現原理
我們在使用 Java 來排程定時任務時,我們經常會使用 Timer 類搞定。Timer 簡單易用,其原始碼閱讀起來也非常清晰,本節我們來仔細分析一下 Timer 類,來看看 JDK 原始碼的編寫者是如何實現一個穩定可靠的簡單排程器。 Timer 使用 Timer 排程任務有一次性排程
【轉】深入瞭解Java程式執行順序
Java中main方法,靜態,非靜態的執行順序詳解1 Java程式執行時,第一件事情就是試圖訪問main方法,因為main相等於程式的入口,如果沒有main方法,程式將無法啟動,main方法更是佔一個獨立的執行緒,找到main方法後,是不是就會執行mian方法塊裡的第一句話呢?答案是不一
深入瞭解java proxy代理
前段時間去阿里面試被問到 java proxy 感覺自己回答的不是很理想,所以打算通過檢視jdk原始碼深入的學習一下java 動態代理; 上程式碼: 先寫一個介面ProxyTest: public interface ProxyTest { void test1
深入瞭解 Java 的 volatile 關鍵字
今天,來談談 Java 併發程式設計中的一個基礎知識點:volatile 關鍵字 本篇文章主要從可見性,原子性和有序性進行講解 一. 主存與工作記憶體 說 volatile 之前,先來聊聊 Java 的記憶體模型。 在 Java 記憶體模型中,規定了所有的變數都是儲存在主記憶體當中,而每個執行
深入瞭解Java併發——《Java Concurrency in Practice》14.構建自定義的同步工具
雖然章節的目的是介紹如何基於AQS等基類來構建自定義的同步工具,但詳細的介紹了AQS的原理,並且詳細的講解了java.util.concurrent類庫中許多基於AQS的常用同步工具對AQS的實現及原理。瞭解AQS之後對ReentrantLock、Sema
從一道面試題深入瞭解java虛擬機器記憶體結構
記得剛大學畢業時,為了應付面試,瘋狂的在網上刷JAVA的面試題,很多都靠死記硬背。其中有道面試題,給我的印象非常之深刻,有個大廠的面試官,順著這道題目,一直往下問,問到java虛擬機器的知識,最後把我給問住了。 我當時的表情是這樣的: 後來我有機會面試別人了,也按照他的思路出面試題,很多已經工作了2年的程式設
讀Hadoop3.2原始碼,深入瞭解java呼叫HDFS的常用操作和HDFS原理
> 本文將通過一個演示工程來快速上手java呼叫HDFS的常見操作。接下來以建立檔案為例,通過閱讀HDFS的原始碼,一步步展開HDFS相關原理、理論知識的說明。 > 說明:本文件基於最新版本Hadoop3.2.1 # 目錄 ### 一、java呼叫HDFS的常見操作 #### 1.1、演示環境搭
深入理解java虛擬機7---線程安全 & 鎖優化
err iou nan gpa egg aik risl cpn hang python%E5%AD%A6%E4%B9%A0%20%20%20%20%20%E5%8F%98%E9%87%8F%E7%9A%84%E6%93%8D%E4%BD%9C%20%E4%B8%8E%20
@Autowired & @Resource 區別 & 解讀@Bean
一樣 Autowired & @Resource 都可以用來Bean的注入,可以寫在屬性上、也可以寫在setter方法上 不一樣 1.來源不一樣 @Autowired 由Spring提供 @Resource 由J2EE提
java 開發 idea spring->resource新增多個資源配置資料夾
環境:dev、test、pre、prod 資料夾:resource-ad、resource-push 最終的樣式如下: 1.先建立如上的資料夾結構,並放置相應的property 2.進入pom.xml,新增相應的資源目錄: <resources>
java.io.FileNotFoundException: class path resource [templates/] spring-cloud-netflix-eureka-server
用main方法啟動spring cloud eureka server工程的時候,報如下錯誤: java.io.FileNotFoundException: class path resource [templates/] cannot be resolv
GeoServer java.io.IOException: No such resource: generic.sld No such resource: generic.sld
-- eos 圖層 默認 但是 根據 settings generic ava 原因是 發布 圖層時 沒有設置類型 默認 generic 但是我們的數據庫中 沒有這個 解決辦法: 點擊 圖層--點擊 相應的 圖層名稱 ---發布 --- WMS Settings 下面
java.io.FileNotFoundException: class path resource ..cannot be opened because it does not exist
java ... mod ons exc pen 方法 except open java.io.FileNotFoundException: class path resource ..cannot be opened because it does not exist