讀取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下的資源文件相信你已經掌握了正確姿勢。
※一文看懂微服務和常用的微服務落地技術
※技術總監到底要不要寫代碼?
TAG:千鋒JAVA開發學院 |