Bootstrap、ExtClassLoader、AppClassLoader
首先先要知道三种类加载器的作用。
启动类加载器(Bootstrap
)
启动类加载器是C++
实现的,负责将<JAVA_HOME>/lib
路径下的核心类库或-Xbootclasspath
参数指定的路径下的jar
包加载到内存中,出于安全考虑,Bootstrap
只加载包名为java、javax、sun
等开头的类
扩展类加载器(ExtClassLoader
)
ExtClassLoader
是Java
实现的,是sun.misc.Launcher
的静态内部类,负责加载<JAVA_HOME>/lib/ext
目录下或者由系统变量-Djava.ext.dir
指定路径中的类库
系统类加载器(AppClassLoader
)
AppClassLoader
也是Java
实现的,同样也是sun.misc.Launcher
的静态内部类,负责加载系统类路径java -classpath
或-D java.class.path
指定路径下的类库。我们口中常说的classpath
就是AppClassLoader
负责加载的。通过ClassLoader#getSystemClassLoader()
方法可以获取到该类加载器
ClassLoader加载资源
我们启动服务时,IDE
会输出当前的Java
环境和-classpath
,-classpath
指定了所有JVM
要加载的文件和路径,里面一定包含了你当前项目编译后的路径target/classes
路径。
单元测试的-classpath
先指定了target/test-classes
,然后是target/classes
这就意味着,使用ClassLoader
在单元测试获取资源和正常服务获取资源有差别。
下面是用web
服务做的测试
classpath
-classpath
只有target/classes
路径,加载资源配置文件时,如果不写资源名,只写./、""、/
时
-
src
下的main
函数中使用ClassLoader
和ClassPathResource
获取资源文件
使用ClassLoader#getResource(String name)
时name=./
返回target/classpath/
name=""
返回在target/classpath/
name=/
返回null
使用
new ClassPathResource(String path)
时
path=/、./、""
,都表示target/classes/
,对于path=/
,Spring
做了处理 -
启动的服务,使用
ClassLoader
和ClassPathResource
获取资源文件
对于/、./、""
,ClassLoader
和ClassPathResource
都表示target/classes/
test-classpath
-classpath
先是target/test-classes
,然后是target/classes
,加载资源配置文件时,如果不写资源名,只写./、""、/
时
- 单元测试的
main
函数中使用ClassLoader
和ClassPathResource
获取资源文件 - 单元测试方法中调用服务,服务中使用
ClassLoader
和ClassPathResource
获取资源文件 - 单元测试方法中使用
ClassLoader
和ClassPathResource
获取资源文件
以上三种,都是优先从target/test-classes
加载相应的资源文件,如果没有,再去target/classes
下加载资源。
使用ClassLoader
时,./和""
返回target/test-classes
,/
返回null
使用ClassPathResource
时,/、./和""
都返回target/test-classes
ClassLoader#getResource(String name)和Class#getResource(String name)的异同
ClassLoader#getResource(String name)
ClassLoader
使用双亲委派模型去查找文件资源,先Bootstrap
,再ExtClassLoader
,最后AppClassLoader
,返回的是target/classes
或者target/test-classes
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;
}
Class#getResource(String name)
源码中先将文件路径定位到当前文件所在的路径下,然后优先获取用户使用的Class
中的ClassLoader
,如果为null
(一般情况下都是AppClassLoader
),调用ClassLoader.getSystemResource(name);
方法,这个方法中调用了ClassLoader#getSystemClassLoader()
,此方法通常返回的是AppClassLoader
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);
}
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;
}