//1.实例方法
public URL getResource(String name)
//这个方法仅仅是调用getResource(String name)返回URL实例直接调用URL实例的openStream()方法
public InputStream getResourceAsStream(String name)
//这个方法是getResource(String name)方法的复数版本,返回匹配name的所有资源路径
public Enumeration getResources(String name) throws IOException
//2.静态方法
public static URL getSystemResource(String name)
//这个方法仅仅是调用getSystemResource(String name)返回URL实例
//直接调用URL实例的openStream()方法
public static InputStream getSystemResourceAsStream(String name)
//这个方法是getSystemResources(String name)方法的复数版本
public static Enumeration getSystemResources(String name)
总的来看,只有两个方法需要分析:getResource(String name)
和getSystemResource(String name)
。
getResource(String name)
的源码://java.lang.ClassLoader.getResource方法
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);//java.net.URLClassLoader.findResource
}
return url;
}
这里明显就是使用了类加载过程中类似的双亲委派模型进行资源加载,这个方法在API注释中描述通常用于加载数据资源如images、audio、text等等,资源名称路径分隔需要使用路径分隔符/
, 但是资源路径不能使用/
字符开始。
getResource(String name)
方法中查找的根路径我们可以通过下面方法验证:
ClassLoader classLoader = ResourceLoader.class.getClassLoader();
URL resource = classLoader.getResource("");
System.out.println(resource);
//输出:路径是 classpath
//file:/D:/Dev/WorkStation/Multthreadinaction/target/classes/
输出的结果就是当前应用的ClassPath,总结来说:ClassLoader.getResource(String name)
是基于用户应用程序的ClassPath搜索资源,资源名称必须使用路径分隔符’/‘去分隔目录,但是不能以’/'作为资源名的起始.
默认情况下 没有第三方实现的classloader去加载classpath资源,findResource方法只有一个java.net.URLClassLoader.findResource
实现
//java.net.URLClassLoader.findResource
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction() {
public URL run() {
//从ucp.path目录中包含所有可能的classpath路径,
//ucp.findResource遍历ucp所有 loaders 属性和name参数尝试拼接在一起
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
遍历ucp 所有 loaders 属性
//sun.misc.URLClassPath.findResource
//参数是: name, true
public URL findResource(String var1, boolean var2) {
int[] var4 = this.getLookupCache(var1);
URLClassPath.Loader var3;
// 遍历ucp.loaders 属性, 对每个 Loader 有 base,属性,这个属性记录了classpath的绝对路径存储在base属性。
for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
URL var6 = var3.findResource(var1, var2);//var1 =name,var2 = true
if (var6 != null) {
return var6;
}
}
return null;
}
在var3.findResource方法中:
//sun.misc.URLClassPath.Loader.findResource 内部类,对每个Loader.base + name属性拼接起来URL 检查是否可以 打开链接。
//参数是: name, true
URL findResource(String var1, boolean var2) {
URL var3;
try {
// base 就是记录的classpath 绝对路径属性URL, var1就是相对于classpath的相对路径,拼接起来如果
var3 = new URL(this.base, ParseUtil.encodePath(var1, false));
} catch (MalformedURLException var7) {
throw new IllegalArgumentException("name");
}
URLConnection var4 = var3.openConnection(); //如果能返回连接说明文件资源真实存在,查找到了。
...
return var3;//返回资源URL链接
public static URL getSystemResource(String name) {
////实际上Application ClassLoader一般不会为null
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResource(name);
}
return system.getResource(name);
}
此方法优先使用应用程序类加载器进行资源加载,如果应用程序类加载器为null(其实这种情况很少见),则使用启动类加载器进行资源加载。如果应用程序类加载器不为null的情况下,它实际上退化为ClassLoader#getResource(String name)
方法。
ClassLoader提供的资源加载的方法中的核心方法是ClassLoader#getResource(String name)
,它是基于用户应用程序的ClassPath路径作为基路径去搜索资源,遵循"资源加载的双亲委派模型",资源名称必须使用路径分隔符’/‘去分隔目录,但是不能以’/'作为资源名的起始字符,其他几个方法都是基于此方法进行衍生,添加复数操作等其他操作。getResource(String name)方法不会显示抛出异常,当资源搜索失败的时候,会返回null。
public java.net.URL getResource(String name) {
name = resolveName(name);
//获取的是ApplicationClassLoader
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
//获取的是ApplicationClassLoader
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
从上面的源码来看,Class#getResource(String name)
和Class#getResourceAsStream(String name)
分别比ClassLoader#getResource(String name)
和ClassLoader#getResourceAsStream(String name)
只多了一步,就是搜索之前先进行资源名称的预处理resolveName(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;
}
org.vincent.res.ResourceLoader.class
中调用了ResourceLoader.class.getResource("123.md")
,那么这个调用的处理资源名称的结果就是org/vincent/res/123.md
package org.vincent.res;
import java.net.URL;
/**
* @author PengRong
* @package org.vincent.res
* @ClassName ResourceLoader.java
* @date 2019/3/25 - 7:20
* @ProjectName Multthread-in-action
* @Description: JDK 资源加载
*/
public class ResourceLoader {
public static void main(String[] args) {
ClassLoader classLoader = ResourceLoader.class.getClassLoader();
URL resource = classLoader.getResource("spring/123.md");
System.out.println(resource);
resource = classLoader.getResource("");
System.out.println(resource);
resource = classLoader.getSystemResource("spring/123.md");
System.out.println(resource);
/** classLoader 加载资源路径 不能以 / 字符开始*/
resource = classLoader.getResource("/spring/123.md");
System.out.println(resource);
/** Class类加载资源文件:可以以 / 字符开始,那么他的 实际寻找路径被resolveName方法处理后变成classpath下 spring/123.md*/
resource = ResourceLoader.class.getResource("/spring/123.md");
System.out.println(resource);
/** Class类加载资源文件: 可以不以 / 字符开始,那么被resolveName方法处理后 实际寻找路径也是classpath下,只是会增加上当前类package全路径 org/vincent/res/123.md*/
resource = ResourceLoader.class.getResource("123.md");
}
}
通过源码浅析Java中的资源加载