Java 加載資源文件

簡介

Java中獲取資源的最經常使用的2中方式就是使用Class的getResource和使用ClassLoader的getResource方法,固然還有它們相關的方法。這裏就介紹一下使用這2中方式的區別,和它們搜索使用的路徑。 這裏先說結論(hotspot): ClassLoader的getResource(name)方法會依次查找:java

  1. 在"sun.boot.class.path"指定的路徑問根目錄下查找name資源
  2. 在"java.ext.dirs"指定的路徑爲根目錄下查找name資源
  3. 在"java.class.path"指定的路徑爲根目錄下查找name資源
  4. 利用ClassLoader(自定義的,重寫了findResource)的findResource(name)獲取URL

Class的getResource(name)方法是調用的ClassLoader的getResource(name)方法,可是它作了2點處理:web

  1. 若是name以"/"開頭,就把name中開頭的"/"去掉,而後調優ClassLoader的getResource(name)方法。而後在ClassLoader的getResource(name)方法搜索方式搜索。
  2. 若是name不以"/"開頭,那麼就用Class的包名+name做爲新的name來調用ClassLoader的getResource(name)方法。cn.freemethod.start.BaseName.class.getResource("config.properties")最終調用的就是ClassLoader的getResource("cn/freemethod/start/config.properties")可能ClassLoader的類型不一樣,可是不影響ClassLoader的getResource(name)搜索套路。(前提是沒有破壞父類委託機制)

注意:這些路徑包括了jar包中的資源,例如,你的classpath中包含了springmvc的jar包,你就能夠經過下面的方式來加載DispatcherServlet.properties文件。spring

import java.net.URL;
import org.junit.Test;

public class ResourceTest {
    
    private static final String DISPATCHER_SERVLET_PROPERTIES_PKG = 
"org/springframework/web/servlet/DispatcherServlet.properties";

    @Test
    public void testResource()
    {
        URL url = null;
        ClassLoader loader = ResourceTest.class.getClassLoader();
        url = loader.getResource(DISPATCHER_SERVLET_PROPERTIES_PKG);
        System.out.println(url);
        url = ClassLoader.getSystemResource(DISPATCHER_SERVLET_PROPERTIES_PKG);
        System.out.println(url);
    }

}

另外一點值得注意的是在IDE中,好比Eclipse中運行的時候Eclipse經常是添加了新的classpath路徑的,好比,若是是Java project工程,Eclipse就會把項目根目錄下的bin目錄添加到classpath中,若是是maven工程,Eclipse就會把項目根目錄下的target目錄下的classes和pom中的jar包加到classpath中,運行JUnit,Eclipse會把test-classes和pom中的jar包加到classpath中。 要知道指定的路徑有沒有在搜索路徑中,能夠輸出3個系統屬性看一下就能夠了:數組

System.out.println(System.getProperty("java.class.path"));
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));

其實用的最多的仍是"java.class.path",通常注意一下"java.class.path"這個就能夠了,知道了上面的內容基本上遇到getResource相關的內容的問題基本都能解決了。接下來咱們就經過代碼來看一些細節的東西。緩存

ClassLoader#getResource(String name)

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;
    }

上面就是ClassLoader的getResource方法了,看上去感受邏輯很簡單,其實仍是比較繞的。若是沒有破壞父類委託機制,那麼調用棧中應該先經過getBootstrapResource(name)這個方法查找,爲了專一與ClassLoader的getResource方法的邏輯,咱們先不細說getBootstrapResource(name),咱們先看一下它幹了什麼,getBootstrapResource(name)方法作的工做就是在"sun.boot.class.path"指定的路徑和其餘經過接口添加到啓動類加載器搜索路徑中查找name。通常咱們把資源放在classpath路徑下,因此,通常是查找不到的。那麼接下來執行到的將是getBootstrapResource(name)方法,層層委託先執行啓動類加載器的getBootstrapResource(name),而後擴展類加載器的getBootstrapResource(name),而後系統類加載器的getBootstrapResource(name),最後自定義類加載器的getBootstrapResource(name)(這裏假設的使用的自定義或者系統類加載器,若是不是,就不搜索下層的加載路徑)。擴展類加載器的getBootstrapResource(name)乾的事情就是在"java.ext.dirs"指定的路徑和其餘經過接口添加到擴展類加載器搜索路徑中查找name。系統類加載器的getBootstrapResource(name)乾的事情就是在"java.class.path"指定的路徑和其餘經過接口添加到系統類加載器搜索路徑中查找name。 若是有興趣的朋友能夠看一下Launcher,URLClassLoader,URLClassPath這3個類的源碼,若是深刻一點還能夠看一下MetaIndex緩存、URLStreamHandler相關的類等。mvc

public Enumeration<URL> getResources(String name) throws IOException {
        Enumeration[] tmp = new Enumeration[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);

        return new CompoundEnumeration<>(tmp);
    }

ClassLoader的getResource是找到一個就不找了,而getResources把全部搜索路徑中能找到的name資源都找出來。maven

ClassLoader提供的一下靜態的查找資源方法

public static URL getSystemResource(String name) {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResource(name);
        }
        return system.getResource(name);
    }

這個方法其實仍是調用的仍是按ClassLoader的套路來查找,不過調用的加載器已經肯定了是系統類加載器(AppclassLoader)而已。this

private static URL getBootstrapResource(String name) {
        URLClassPath ucp = getBootstrapClassPath();
        Resource res = ucp.getResource(name);
        return res != null ? res.getURL() : null;
    }

只在"sun.boot.class.path"路徑下查找name資源。url

static URLClassPath getBootstrapClassPath() {
        return sun.misc.Launcher.getBootstrapClassPath();
    }
public static Enumeration<URL> getSystemResources(String name)
        throws IOException
    {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResources(name);
        }
        return system.getResources(name);
    }

和getSystemResource(String name)差很少,只是查找搜索路徑下全部的name資源而已。spa

private static Enumeration<URL> getBootstrapResources(String name)
        throws IOException
    {
        final Enumeration<Resource> e =
            getBootstrapClassPath().getResources(name);
        return new Enumeration<URL> () {
            public URL nextElement() {
                return e.nextElement().getURL();
            }
            public boolean hasMoreElements() {
                return e.hasMoreElements();
            }
        };
    }

和getBootstrapResource(String name),只是查找"sun.boot.class.path"下全部的name資源而已。

Class#getResource(String name)

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的獲取資源的時候都是使用的咱們自定義的類,因此ClassLoader通常都是系統類加載器或者咱們自定義的類加載器,因此getResource(String name)基本上就是按照ClassLoader的基本的套路來搜索的。最多見的用法就是加載和Class在同一個包下的資源,這樣就不用拼接路徑了。Class#getResource("/xxx"),加上"/"這個樣的方式基本上就和ClassLoader#getSystemResource(String name)這個靜態方法差很少了。

Class#resolveName(String name)

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
                name = baseName.substring(0, index).replace('.', '/')
                    +"/"+name;
            }
        } else {
            name = name.substring(1);
        }
        return name;
    }
相關文章
相關標籤/搜索