Java的官方文档说明中讲到:classloader装载类时的优先级(-jar命令参数 > -classpath命令参数 > CLASSPATH环境变量 > 默认的'.'指定的当前路径)。详细可参考官方文档How Classes are Found一文。
System.out.println(PhantomReferenceDemo.class.getResource("./"));
//获取当前类的相对路径
System.out.println(PhantomReferenceDemo.class.getResource(""));
//获取当前类的绝对根路径:类路径的根路径
System.out.println(CustomResource.class.getResource("/"));
输出:
file:/Users/zhfeng/mayun/sun-jdk/base/target/classes/net/base/reference/
file:/Users/zhfeng/mayun/sun-jdk/base/target/classes/net/base/reference/
file:/Users/zhfeng/mayun/sun-jdk/base/target/classes/
getResource & getResourceAsStream:加载路径其实都是一样的,只不过结果一个表示URL,另一个为InputStream。
1.ClassLoader加载资源公共逻辑
切记:只要是借助类加载器加载系统资源则一定是以类路径classpath为根路径。
public abstract class ClassLoader {
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
//URLClassLoader#findResource
url = findResource(name);
}
return url;
}
}
如果最终资源是借助 URLClassLoader 类加载器处理,则name属性一定是相对路径,而且是相对于类路径classpath。
public class URLClassLoader{
public URL findResource(final String name) {
// 因为URL是指类路径classpath下某个资源,所以name属性一定是类路径下的子路径,否则url为null
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
}
默认情况下都是通过 系统类加载器 加载指定的资源。
2.Class类获取系统资源
URL resource = CustomResource.class.getResource("/test.txt");
URL resource = CustomResource.class.getResource("test.txt");
InputStream inputStream = resource.openStream();
绝对路径:类路径classpth【根路径】下寻找test.txt资源。
相对路径:类路径classpth【根路径】 + 当前类CustomResource全限定性名下寻找test.txt资源。
public final class Class<T>{
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);// 利用类加载器URLClassLoader加载资源
}
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {//name属性不以"/"开头
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
// 获取当前类的全限定性类名:包名 + class名
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
// 将 当前类的 baseName 拼接在 目标路径 name之前
name = baseName.substring(0, index).replace('.', '/') +"/"+name;
}
} else {// 存在以"/"开头,则直接去掉"/"
name = name.substring(1);
}
return name;//返回的路径name一定是相对路径,即路径开头没有"/"
}
}
3.ClassLoader类加载资源
URL resource = ClassLoader.getSystemClassLoader().getResource("test.txt");
System.out.println(resource.getPath());
read(resource.openStream());
URL systemResource = ClassLoader.getSystemResource("test.txt");
read(systemResource.openStream());
URL systemResource1 = ClassLoader.getSystemResource("net/base/resource/test.txt");
read(systemResource1.openStream());
通过 ClassLoader 加载系统资源中间不存在类Class#resolveName帮忙解析路径,所以用户自己设定相对于根路径(classpath)的相对路径。
public abstract class ClassLoader {
public static URL getSystemResource(String name) {
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResource(name);
}
// 系统类加载器 -> ClassLoader#getResource
return system.getResource(name);
}
}
默认情况下最终还是通过 URLClassLoader 加载资源。
4.自定义类加载系统资源
// 基于类路径下classpath加载资源
URL resource = CustomResource.class.getResource("/test.txt");
read(resource.openStream());
// 拼接类的全限定性名 + CustomResource类名
URL resource1 = CustomResource.class.getResource("test.txt");
read(resource1.openStream());
其实,最终是通过Class类提供的相关api加载资源。
URL resource = CustomResource.class.getClassLoader().getResource("test.txt");
read(resource.openStream());
// 拼接类的全限定性名 + CustomResource类名
URL resource1 = CustomResource.class.getClassLoader().getResource("net/base/resource/test.txt");
read(resource1.openStream());
其实,最终是通过类ClassLoader提供的相关api加载资源。