Java中獲取資源的最經常使用的2中方式就是使用Class的getResource和使用ClassLoader的getResource方法,固然還有它們相關的方法。這裏就介紹一下使用這2中方式的區別,和它們搜索使用的路徑。 這裏先說結論(hotspot): ClassLoader的getResource(name)方法會依次查找:java
Class的getResource(name)方法是調用的ClassLoader的getResource(name)方法,可是它作了2點處理:web
注意:這些路徑包括了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相關的內容的問題基本都能解決了。接下來咱們就經過代碼來看一些細節的東西。緩存
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
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資源而已。
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)這個靜態方法差很少了。
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; }