當前位置:
首頁 > 知識 > 讀取ClassPath下resource文件的正確姿勢

讀取ClassPath下resource文件的正確姿勢

前言

為什麼要寫這篇文章?身為Java程序員你有沒有過每次需要讀取 ClassPath 下的資源文件的時候,都要去百度一下,然後看到下面的這種答案:

CopyThread.currentThread().getContextClassLoader().getResource(ss.properties).getPath();

亦或是:

CopyObject.class.getResourceAsStream(ss.properties);

你複製粘貼一下然後放到自己的項目里運行,還真跑起來了。但是當打成 jar 包作為其它項目的依賴時,或者打成 war 包被 Tomcat 載入時,你還能保證你的resources 資源文件被讀取到嗎?答案是不能的。

其中的原因如何而又如何解決,究竟怎樣才能寫出萬無一失根本不用擔心任何環境的代碼?個中原委,請聽我一一道來。

2.再看類載入機制

看到這個標題你也許會有些意外,不是說的讀取ClassPath下的文件嗎?為什麼要講類載入機制。

不知你有沒有想過,ClassPath下的資源文件標準存放的是什麼?顧名思義,是 .class 類文件。為什麼我們的類可以被正確載入到Java虛擬機(JVM),而自己添加的資源文件卻載入失敗呢?歸根結底是你沒有理解類載入機制,也就無法做到舉一反三。

類載入機制與類載入器

程序員將源代碼寫入.Java文件中,經過(javac)編譯,生成.class二進位文件。虛擬機把描述類的數據從Class文件載入到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類載入機制。

從宏觀上理解了類載入機制後,接下來就要從細節上說一說類載入器,以及類載入器的工作原理。

類載入器,顧名思義,是載入類的器件。JVM只存在兩種不同的類載入器:啟動類載入器(Bootstrap ClassLoader),使用C 實現,是虛擬機自身的一部分。另一種是所有其他的類載入器,使用JAVA實現,獨立於JVM,並且全部繼承自抽象類java.lang.ClassLoader。包括擴展類載入器、應用程序類載入器。

它我們在寫代碼時,總是會new很多對象,我們之所以可以new出對象,是因為該對象對應的類已經被JVM載入為Class類的對象實例。這句話有點繞,我用代碼展示一下:

CopyObj obj = new Obj(); //Obj對象實例Class o = obj.getClass(); //Obj類是Class類的對象實例

在JVM中,一般情況下,我們的類的類實例是唯一的,這得益於類載入機制的雙親委派模型。

如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都是應該傳送到頂層的啟動類載入器中,只有當父類載入器反饋自己無法完成這個載入請求(它的搜索範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入。

3.類也是一種Resource

言歸正傳,通過上述對類載入機制的學習,我們可以得出這樣的一個結論:一個類文件是由某個類載入器負責載入到JVM中的,且只會有一個類載入器去載入。反過來說,由一個類實例就可以獲取到載入它到JVM中的那個類載入器。

用代碼闡述我的上段話如下所示:

CopyObj obj = new Obj();

ClassLoader classLoader = obj.getClass().getClassLoader();

跟著我的思路繼續走,該類載入器之所以可以載入這個類,是因為這個類在該類載入器的搜索範圍內。類載入器既然可以載入這個類文件,那麼也可以載入該類文件同級目錄下的所有資源文件。

所以,我們要想確保可以讀取到某個資源文件,只需調用和該資源文件在同一目錄下的類的Class對象的getClassLoader()方法獲取該類載入器即可。

舉個例子,我們有一個properties文件和Obj.class在同一個目錄下, 那我們讀取該properties文件的最正確的方式就是通過Obj.class.getClassLoader().getResourceAsStream()方法。

4.一個錯誤的例子

為了印證上面的結論,先看下 Object.class.getResourceAsStream() 的源碼:

Copy// Class.javapublic InputStream getResourceAsStream(String name) {

name = resolveName(name);

ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class.

return ClassLoader.getSystemResourceAsStream(name);

} return cl.getResourceAsStream(name);

}

從 Javadoc 文檔和源碼中可以看出:

Class.getResourceAsStream() 代理給了載入該 class 的 ClassLoader 去實現,調用 classLoader.getResourceAsStream(),如果該類的 ClassLoader 為 null,說明該 class 一個系統 class,所以委託給 ClassLoader.getSystemResourceAsStream。

這一點也印證了之前講解的原理:資源文件都是由ClassLoader負責載入的,類也是一種resources文件。

但通過Object.class.getResourceAsStream()不一定可以搜索到指定的資源文件,原因就在於前面說過的類載入器的搜索範圍,所以這種方式並不推薦使用。

5.FileHelper

最後推薦一個操作Resources資源的框架FileHelper:

Copy

cn.yueshutong

FileHelper

1.0.RELEASE

讀取Resources下的資源

CopyClassPathResource resource = new ClassPathResource();

String html = resource.readString(commons.html,StandardCharsets.UTF_8);

String htm = resource.readString(commons.htm);byte[] bytes = resource.readByte(commons.html);

InputStream inputStream = resource.read(commons.html);

String resourcePath = resource.getPath(); //獲取resources根目錄

關於如何正確讀取ClassPath下的資源文件相信你已經掌握了正確姿勢。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 千鋒JAVA開發學院 的精彩文章:

一文看懂微服務和常用的微服務落地技術
技術總監到底要不要寫代碼?

TAG:千鋒JAVA開發學院 |