爲何要寫這篇文章?身爲Java程序員你有沒有過每次須要讀取 ClassPath 下的資源文件的時候,都要去百度一下,而後看到下面的這種答案:java
Thread.currentThread().getContextClassLoader().getResource("ss.properties").getPath();
亦或是:程序員
Object.class.getResourceAsStream("ss.properties");
你複製粘貼一下而後放到本身的項目裏運行,還真跑起來了。可是當打成 jar 包做爲其它項目的依賴時,或者打成 war 包被 Tomcat 加載時,你還能保證你的resources 資源文件被讀取到嗎?答案是不能的。編程
其中的緣由如何而又如何解決,究竟怎樣才能寫出萬無一失根本不用擔憂任何環境的代碼?箇中原委,請聽我一一道來。學習
看到這個標題你也許會有些意外,不是說的讀取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中,通常狀況下,咱們的類的類實例是惟一的,這得益於類加載機制的雙親委派模型。
若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都是應該傳送到頂層的啓動類加載器中,只有當父類加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。
言歸正傳,經過上述對類加載機制的學習,咱們能夠得出這樣的一個結論:一個類文件是由某個類加載器負責加載到JVM中的,且只會有一個類加載器去加載。反過來講,由一個類實例就能夠獲取到加載它到JVM中的那個類加載器。
用代碼闡述個人上段話以下所示:
Obj obj = new Obj(); ClassLoader classLoader = obj.getClass().getClassLoader();
跟着個人思路繼續走,該類加載器之因此能夠加載這個類,是由於這個類在該類加載器的搜索範圍內。類加載器既然能夠加載這個類文件,那麼也能夠加載該類文件同級目錄下的全部資源文件。
因此,咱們要想確保能夠讀取到某個資源文件,只需調用和該資源文件在同一目錄下的類的Class對象的getClassLoader()方法獲取該類加載器便可。
舉個例子,咱們有一個properties文件和Obj.class在同一個目錄下, 那咱們讀取該properties文件的最正確的方式就是經過Obj.class.getClassLoader().getResourceAsStream()
方法。
爲了印證上面的結論,先看下 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()
不必定能夠搜索到指定的資源文件,緣由就在於前面說過的類加載器的搜索範圍,因此這種方式並不推薦使用。
關於如何正確讀取ClassPath下的資源文件相信你已經掌握了正確姿式。
我是薛勤,我們下期見!關注我,帶你領略更多編程技能!