1. 程式人生 > >深入瞭解 Java Resource && Spring Resource

深入瞭解 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 ClassLoaderEtxClassLoader,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()獲取的

在平時使用的過程中,有三點需要注意:

  1. classpath 和 classpath* 區別:

    classpath:只會返回第一個查詢到的檔案
    classpath*:會返回所有查詢到的檔案

  2. Spring中,需要直接表示使用ClassPathResource()來查詢的話,可以直接新增classpath:

  3. 使用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 &amp;&amp; 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---線程安全 &amp; 鎖優化

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 &amp; @Resource 區別 &amp; 解讀@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