深刻了解 Java Resource && Spring Resource

Java中,爲了從相對路徑讀取文件,常常會使用的方法即是:html

xxx.class.getResource();

xxx.class.getClassLoader().getResource();

Spring中,咱們還能夠經過Spring提供的Resource進行一些操做:java

ClassPathResource

FileSystemResource

ServletContextResource

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

這裏簡單總結下他們的區別:web


ClassLoader##getResource()

這個方法是今天的主角。spring

咱們都知道ClassLoader的做用是用來加載.class文件的,而且ClassLoader是遵循Java類加載中的雙親委派機制的。api

那麼,ClassLoader是如何找到這個.class文件的呢?答案是URLClassPath微信

Java中自帶了3個ClassLoader分別是BootStrap ClassLoaderEtxClassLoader,AppClassLoader,app

這3個ClassLoader都繼承自URLClassLoader,而URLClassLoader中包含一個URLClassPath用來記錄每一個ClassLoader對應的加載.class文件的路徑,當須要加載資源的時候,只管從URLClassPath對應的路徑查找便可。webapp

下面是測試代碼:ide

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目錄以下:

image

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進階的文章,感謝關注

相關文章
相關標籤/搜索