spring-framework的Resource知識點

黃小數發表於2017-11-02

介面類:org.springframework.core.io.Resource
三個具有代表性的實現類:

  1. org.springframework.web.context.support.ServletContextResource
  2. org.springframework.core.io.ClassPathResource
  3. org.springframework.core.io.UrlResource

通過XmlWebApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)和loadBeanDefinitions(XmlBeanDefinitionReader reader),我們找到ApplicationContext利用ServletContextResourcePatternResolver去解析路徑(配置檔案的路徑)。
上面可能講的有點繞,但卻是入口之一。 我們可以跳出這個細節來看,其實Resource是通過ResourceLoader去載入(名字也能看出啦),而ApplicationContex是ResourceLoader的一個實現。所以Application有getResource()的方法,但具體例項化Resource,ApplicationContex不親自幹,而是用組合的形式交給ResourcePatternResolver去處理,這是因為不同環境尋找配置檔案的形式不一樣,而其中PathMatchingResourcePatternResolver和ServletContextResourcePatternResolver是ResourcePatternResolver的實現,用於不同環境中載入Resource。

ServletContextResourcePatternResolver會將路徑封裝成Resource物件。根據路徑的特性,分別封裝為ServletContextResource、ClassPathResource或UrlResource物件。

這裡個路徑清理的工具類StringUtils.cleanPath,參考《StringUtils知識點》

我們先看一些預設路徑的知識:

package org.springframework.jc;

import org.apache.commons.logging.Log;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

public class ClassGetResourceTest {

    public static void main(String[] args) throws IOException {
        System.out.println("============================================env===================================");
        Map<String, String> map = System.getenv();
        for (Iterator<String> itr = map.keySet().iterator(); itr.hasNext(); ) {
            String key = itr.next();
            System.out.println(key + "=" + map.get(key));
        }
        System.out.println("============================================properties===================================");
        Properties properties = System.getProperties();
        for(String key:properties.stringPropertyNames()){
            System.out.println(key + "=" + properties.get(key));  // user.dir=/home/cherry/git/spring-framework
        }
        System.out.println(ClassGetResourceTest.class.getResource(""));  //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/org/springframework/jc/
        System.out.println(ClassGetResourceTest.class.getResource("/")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/

        //喲喲,不同包下的類,Log.class.getResource("")是不一樣的!!! 可以獲取jar包裡的配置檔案哦,如commons-logging-1.2.jar包
        System.out.println(Log.class.getResource(""));  //jar:file:/home/cherry/.gradle/caches/modules-2/files-2.1/commons-logging/commons-logging/1.2/4bfc12adfe4842bf07b657f0369c4cb522955686/commons-logging-1.2.jar!/org/apache/commons/logging/
        System.out.println(Log.class.getResource("/")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/


        File file = new File("test.txt");
        file.createNewFile();
        System.out.println(file.getAbsolutePath());  //使用user.dir作為根目錄

        System.out.println(ClassGetResourceTest.class.getName() + ".ROOT");

        System.out.println(ClassGetResourceTest.class.getClassLoader());                        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(ClassGetResourceTest.class.getClassLoader().getResource(""));  //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/
                                                                                                //作用和ClassGetResourceTest.class.getResource("/")一樣

        System.out.println(ClassGetResourceTest.class.getClassLoader().getResource("/")); //null
                                                                                                //ClassLoader.getResource不可以使用"/"

        //不同包下的類,結果也與ClassGetResourceTest.class.getClassLoader()一樣,於是不可以獲取jar包裡的配置檔案哦,如commons-logging-1.2.jar包
        System.out.println(Log.class.getClassLoader());                        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(Log.class.getClassLoader().getResource(""));  //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/
                                                                                                //作用和ClassGetResourceTest.class.getResource("/")一樣

        System.out.println(Log.class.getClassLoader().getResource("/")); //null
                                                                                                //ClassLoader.getResource不可以使用"/"


    }
}

從上可以得出一些結論:

  1. user.dir是jvm的系統屬性,預設是取你執行命令時的目錄,也就是說如果在/mydata使用命令 /mydata/jc-test/server.sh start,此時user.dir為/mydata,如果在/mydata/other目錄執行/mydata/jc-test/server.sh start,則此時user.dir為/mydata/other
  2. class.getResource方法,根據是否以根目錄“/”開始來決定檔案目錄:

class.getResource(“”)相對於當前類的目錄,是個相對路徑。例如一些不是公共的配置檔案,僅僅這個類或這個包下使用的配置檔案。 另外用jar包裡的class做了實驗,發現可以讀到jar包裡的資訊。
class.getResource(“/”)則是包的根地方,如…./classes/,用於公共配置檔案。

  1. ClassLoader.getResource方法,不可以根目錄“/”開始來決定檔案目錄,否則為null:

ClassLoader.getResource(“”)則是包的根地方,如…./classes/
ClassLoader.getResource(“”)為null

好了,我們現在迴歸主題,org.springframework.core.io.ClassPathResource能夠根據類或載入器(classload)來載入類路徑的檔案,原文:

Resource implementation for class path resources. Uses either a given ClassLoader or a given Class for loading resources.

核心邏輯:

/**
     * This implementation opens an InputStream for the given class path resource.
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.lang.Class#getResourceAsStream(String)
     */
    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }