关于获取资源文件,Class.getResource 和 ClassLoader.getResource 的区别
彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理
1. ClassLoader.getResource
有两种方法获取当前的ClassLoader
ClassLoader classLoader = this.getClass().getClassLoader();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
我们可以使用获取到的ClassLoader
的getResource(String name)
方法来获取想要的资源文件的URL
,其中要注意:
在使用 ClassLoader().getResource
获取路径时,不能以 "/"
开头,且路径总是从 classpath
根路径开始
使用如下:
public class GetResourceTest {
public void getResourceByClassLoader(){
//sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader classLoader = this.getClass().getClassLoader();
//file:/D:/ideaProject/simplespring/target/classes/
System.out.println(classLoader.getResource(""));
//file:/D:/ideaProject/simplespring/target/classes/com/resourceloadpag/testpag/GetResourceTest.class
System.out.println(classLoader.getResource("com/resourceloadpag/testpag/GetResourceTest.class"));
sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//null
System.out.println(contextClassLoader.getResource("/"));
//file:/D:/ideaProject/simplespring/target/classes/test
System.out.println(contextClassLoader.getResource("test"));
//在获取文件时,我们还可以通过 getResourceAsStream 直接获取文件输入流
InputStream inputStream = contextClassLoader.getResourceAsStream("test");
}
public static void main(String[] args) {
GetResourceTest getResourceTest = new GetResourceTest();
getResourceTest.getResourceByClassLoader();
}
}
2. Class.getResource
对于这个方法,getResource("")
获取的是当前类所在包的路径,而 getResource("/")
获取的是 classpath
根路径
public class GetResourceTest {
public void getResourceByClass(){
//file:/D:/ideaProject/simplespring/target/classes/com/resourceloadpag/testpag/
URL url = this.getClass().getResource("");
System.out.println(url);
//file:/D:/ideaProject/simplespring/target/classes/com/resourceloadpag/testpag/GetResourceTest.class
System.out.println(this.getClass().getResource("GetResourceTest.class"));
//file:/D:/ideaProject/simplespring/target/classes/
System.out.println(this.getClass().getResource("/"));
//file:/D:/ideaProject/simplespring/target/classes/com/resourceloadpag/testpag/GetResourceTest.class
System.out.println(this.getClass().getResource("/com/resourceloadpag/testpag/GetResourceTest.class"));
//null
System.out.println(this.getClass().getResource("test"));
//file:/D:/ideaProject/simplespring/target/classes/test
System.out.println(this.getClass().getResource("/test"));
//在获取文件时,我们还可以通过 getResourceAsStream 直接获取文件输入流
InputStream inputStream = this.getClass().getResourceAsStream("/test");
}
}
总结一下Class.getResource()
和ClassLoader().getResource()
的区别
Class.getResource()
可以从当前Class
所在包的路径开始匹配获取资源,也可以从classpath
根路径开始匹配获取资源;ClassLoader().getResource()
只能从classpath
根路径开始匹配获取资源;Class.getResource()
从当前包所在路径获取资源时不能以"/"
开头,而从classpath
根路径获取资源时必须以"/"
开头;ClassLoader().getResource()
不能以"/"
开头,且路径总是从classpath
根路径开始;- 它们都能通过
getResourceAsStream()
方法获取对应路径文件的输入流,文件路径匹配机制和其getResource()
方法一样;
3. 源码解读
通过源码来比较这两个方法的不同,首先看一下Class.getResource
的源码
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();//获取当前类的类加载器
if (cl==null) {//如果类加载器为null,那么说明是BootstrapClassLoader,当前类是系统类
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
可以看到当前类的类加载器是AppClassLoader
,接下来就是调用当前类加载器的getResource
方法;
也就是说,Class.getResource
和ClassLoader.getResource
实际上都是调用ClassLoader
类的getResource
方法,只不过在Class.getResource
中害有一步resolveName
,下面看一下resolveName
做了什么
/**
* Add a package name prefix if the name is not absolute Remove leading "/"
* if name is absolute
*/
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;
}
这也就是为什么Class.getResource
可以以"/"
开头;
说完了这些,再去看一下ClassLoader.getResource
究竟做了什么
public URL getResource(String name) {
URL url;
if (parent != null) {//先交给父亲处理(双亲委派机制)
url = parent.getResource(name);
} else {//到了BootstrapClassLoader系统启动类加载器
url = getBootstrapResource(name);
}
if (url == null) {//最后如果都没有加载到,双亲委派加载失败,则加载应用本身自己的加载器
url = findResource(name);
}
return url;
}