简介
Java中获取资源的最常用的2中方式就是使用Class的getResource和使用ClassLoader的getResource方法,当然还有它们相关的方法。这里就介绍一下使用这2中方式的区别,和它们搜索使用的路径。 这里先说结论(hotspot): ClassLoader的getResource(name)方法会依次查找:
- 在"sun.boot.class.path"指定的路径问根目录下查找name资源
- 在"java.ext.dirs"指定的路径为根目录下查找name资源
- 在"java.class.path"指定的路径为根目录下查找name资源
- 利用ClassLoader(自定义的,重写了findResource)的findResource(name)获取URL
Class的getResource(name)方法是调用的ClassLoader的getResource(name)方法,但是它做了2点处理:
- 如果name以"/"开头,就把name中开头的"/"去掉,然后调优ClassLoader的getResource(name)方法。然后在ClassLoader的getResource(name)方法搜索方式搜索。
- 如果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文件。
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相关的类等。
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资源都找出来。
ClassLoader提供的一下静态的查找资源方法
public static URL getSystemResource(String name) {
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResource(name);
}
return system.getResource(name);
}
这个方法其实还是调用的还是按ClassLoader的套路来查找,不过调用的加载器已经确定了是系统类加载器(AppclassLoader)而已。
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资源。
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资源而已。
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;
}
来源:oschina
链接:https://my.oschina.net/u/2474629/blog/799182