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

1.前言

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

Thread.currentThread().getContextClassLoader().getResource("ss.properties").getPath();

亦或是:程序員

Object.class.getResourceAsStream("ss.properties");

你複製粘貼一下而後放到本身的項目裏運行,還真跑起來了。可是當打成 jar 包做爲其它項目的依賴時,或者打成 war 包被 Tomcat 加載時,你還能保證你的resources 資源文件被讀取到嗎?答案是不能的。編程

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

2.再看類加載機制

看到這個標題你也許會有些意外,不是說的讀取ClassPath下的文件嗎?爲何要講類加載機制。代理

其實你有沒有想過,ClassPath下的資源文件標準存放的是什麼?顧名思義,是 .class 類文件。爲何咱們的類能夠被正確加載到Java虛擬機(JVM),而本身添加的資源文件卻加載失敗呢?歸根結底是你沒有理解類加載機制,也就沒法作到觸類旁通。code

類加載機制與類加載器

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

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

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

它咱們在寫代碼時,老是會new不少對象,咱們之因此能夠new出對象,是由於該對象對應的類已經被JVM加載爲Class類的對象實例。這句話有點繞,我用代碼展現一下:資源

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

在JVM中,通常狀況下,咱們的類的類實例是惟一的,這得益於類加載機制的雙親委派模型。

若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都是應該傳送到頂層的啓動類加載器中,只有當父類加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。

3.類也是一種Resource

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

用代碼闡述個人上段話以下所示:

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

跟着個人思路繼續走,該類加載器之因此能夠加載這個類,是由於這個類在該類加載器的搜索範圍內。類加載器既然能夠加載這個類文件,那麼也能夠加載該類文件同級目錄下的全部資源文件。

因此,咱們要想確保能夠讀取到某個資源文件,只需調用和該資源文件在同一目錄下的類的Class對象的getClassLoader()方法獲取該類加載器便可

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

4.一個錯誤的例子

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

// Class.java
public 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.結語

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

我是薛勤,我們下期見!關注我,帶你領略更多編程技能!

相關文章
相關標籤/搜索