关于Spring Boot中的getResources
在前一篇讲Spring Boot启动流程的文章中,在获取Initializers和Listeners的时候都会通过ClassLoader的getResources方法获取相关文件的全限定名。
今天着重分析一下ClassLoader.getResources,ClsasLoader.getResource,Class.getResource
1.Class.getResource
看下面一段代码
System.out.println(TestResources.class.getResource("Test.xml"));
System.out.println(TestResources.class.getResource("/Test.xml"));
返回结果:
null
file:/Users/xujingzhou/IdeaProjects/test-resources/target/classes/Test.xml
我们先看现象,同样都是调用调用class.getResource方法,为什么会有不同的结果呢。
首先看一下class.getResource()
public java.net.URL getResource(String name) {
name = resolveName(name);
//获取当前的类加载器
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
//调用系统类加载器
return ClassLoader.getSystemResource(name);
}
//调用classLoader的getResource方法
return cl.getResource(name);
}
首先调用的是resolveName(),根据我们传入的参数一个加‘/’一个没加,刚好在resolveName中存在判断。
private String resolveName(String name) {
if (name == null) {
return name;
}
//如果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;
}
看到这里,我们应该去看看ClassLoader中的getResource是如何实现的了,先做个小实验吧。
System.out.println(TestResources.class.getClassLoader().getResource("Test.xml"));
System.out.println(TestResources.class.getClassLoader().getResource("/Test.xml"));
输出结果:
file:/Users/xujingzhou/IdeaProjects/test-resources/target/classes/Test.xml
null
先看结论,当传入的name为文件名的时候,是可以找到当前文件的全限定名的。但是为路径的时候就找不到了,接下来就看看是如何实现的。
public URL getResource(String name) {
URL url;
//如果父加载器不为null,则调用父加载器,符合双亲委派机制
if (parent != null) {
url = parent.getResource(name);
//这其实是一个递归调用,最终都会走到这一步调用系统启动类加载器
} else {
url = getBootstrapResource(name);
}
//一个递归回退的过程,都会走一遍findResource方法
if (url == null) {
url = findResource(name);
}
return url;//如果最后都没加载到,双亲委派机制失败,加载应用自身的类加载器
}
可是这样做的目的是为了什么呢
现在主要分为两种情况
- name前加‘/’
- TestClassLoader.class.getResource("/")
- Class类中的getResource方法返回的是""
- ClassLoader类中的getResource方法返回的是 路径
- TestClassLoader.class.getClassLoader().getResource("/")
- ClassLoader类中的getResource方法返回的是 null
- TestClassLoader.class.getResource("/")
- name前不加‘/’
- TestClassLoader.class.getResource("")
- Class类中的getResource方法返回的是 路径
- ClassLoader类中的getResource方法返回的是 路径
- TestClassLoader.class.getClassLoader().getResource("")
- ClassLoader类中的getResource方法返回的是 路径
- TestClassLoader.class.getResource("")
-
class的getResource
- 当path不以‘/’开头我们就可以获得和当前类在路径相同的文件,也就是我们说的同级
- 当path以‘/’开头时,这样就可以获取到classpath下任意路径下的资源
-
classLoader的getResource
- 当path不以‘/’开头的时候,首先通过双亲委派机制,逐级向上委托的形式加载,最后发现双亲没有加载,然后通过当前类加载classpath下资源文件
- 当path以‘/’开头时,‘/’表示boot classLoader中的加载范围,这个类是CPP写的,所以加载范围是null。