在 Java 中访问资源我们一般使用 getResource()
方法,亦或者直接new File()
然后传入一个文件路径获取资源文件。但是这两者究竟有什么区别呢?由于平常在使用的时候经常会傻傻分不清楚,因此这里写篇博客整理下我的笔记。
Java 运行中资源的说明
这里我们将运行中 Java 进程可以获取到的资源简单分为两类:
存在于文件系统中的资源
这类资源一般直接存在与磁盘中,我们可以直接从资源管理器中访问到。当我们通过应用访问这类资源时既可以使用 File
对象通过文件系统获取到,也可以使用 getResource()
方法获取到(仅限于classpath中)。
注意
getResource()
方法的获取范围仅限于 指定的范围classpath
,classpath
之外的资源是无法获取到的。
存在于jar
包中的资源
这类资源如字面含义,存在于 jar 包中,而 jar
是被我们包含在 classpath
中的。所以这一类资源妥妥的可以被我们称为代码中的资源。由于这种资源是包含在一个文件中,我们用File
只能获取到jar
包,而无法获取到 jar
包中的资源。
这类资源一般是存在于 classpath
中的文件,我们可以使用上面的 getResource()
方法获取到其路径,(通常样式是资源所在文件的路径+资源在文件中的路径)但是没有办法直接通过文件读取到。但是可以通过 classLoader 对象中的 getResourceAsStream()
方法获取到资源的输入流。
通过 getResourceAsStream()
方法获取资源的输入流实际上是通过 URL 对象,通过 openStream()
方法打开的,因此我们只需要拿到正确的资源URL 地址,就能够获得该类资源的输入流了。
Java 中 getResource()
说明
在 Java 中类对象如java.lang.String.class
或者 具体的类加载器对象如 java.lang.String.class.getClassLoader()
中都有getResource()
方法,但是在类对象上调用getResource()
方法时,路径会和使用类加载器对象上调用getResource()
方法有所区别。
当getResource()
方法中传入的路径为绝对路径时,两者都是从 classpath 开始寻找资源:
Main.class.getResource("/com/ghimi/demo/data.json");
Main.class.getClassLoader().getResource("/com/ghimi/demo/data.json");
// 两者实际获取到的资源相同
当getResource()
方法中传入的路径为相对路径时,两者查找资源的方式会有所差异
// 当使用类对象加载资源时,会从类对象目录下去寻找该资源
// 如 Main.class 位于 demo01.jar 包的 /com/ghimi/demo 目录下
// getResource 方法会尝试从该目录下查找 data.json 资源
Main.class.getResource("data.json");
// 当使用类加载器对象加载资源时,会从 classpath 根目录下查找该资源
// getResource 方法会尝试从 classpath 根目录下找 data.json 资源
Main.class.getClassLoader().getResource("data.json");
为什么会导致这样的差异呢,现在我们打开源码,去分析一下这 class 对象上的 getResource()
方法与 classLoader
对象上的 getResource()
方法在实现上的区别。
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);
}
return cl.getResource(name);
}
方法开始,调用了一个 resolveName(name)
方法对入参 name
做了一下处理,而 resolveName(name)
方法是这样实现的:
private String resolveName(String name) {
if (name == null) {
// 当入参为 null 时 返回 null
return name;
}
if (!name.startsWith("/")) {
// 当 name 不以 "/" 开头时,表示这是一个相对路径,则进行如下处理
Class<?> c = this;
// 判断当前 class 对象是否是一个数组对象
while (c.isArray()) {
// 如果当前 class 对象是一个数组对象,则取数组内 class 类型
c = c.getComponentType();
}
// 获取当前 class 对象的全限定类名如 java.lang.String
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
// 取当前 class 对象的包名如 java.lang
// 将 . 替换为 "/" 路径名
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
// 当name以 "/" 开头时,表示这是一个绝对路径,则返回去掉 "/" 的 name
name = name.substring(1);
}
return name;
}
也就是说,我们在调用 class 对象的 getResource()
方法时,会首先对入参进行处理。且处理方式与两种因素存在关系:
- 输入参数是否是一个绝对路径
- 当前
class
对象的包路径
而在处理入参时,处理逻辑如下:
可以看到,类对象上的getResource() 方法在对资源路径处理时,如果是相对路径或者直接的资源名,就会根据当前类对象的包路径进行处理,也即会认为资源与代码在相同路径下。
总结:class 对象在获取资源时,如果使用相对路径,则认为资源与 class对象处于相同路径下
也就是资源位置与代码位置相关,当代码处于磁盘上时,会认为资源也位于磁盘上;
使用 getResource()
方法获取到的 URL
file:/D:/Desktop/code/demo09/com/ghimi/demo/data.json
当代码处于jar包中时,资源也会被认为处于 jar包中。
使用 getResource()
方法获取到的 URL
jar:file:/D:/Desktop/code/demo09/demo09.jar!/com/ghimi/demo/data.json
实际上 class对象的 getResource()
方法最终调用的方法也是 classLoader对象中的 getResource()
方法。下面我们来看下 classLoader
对象中 getResource()
方法
classLoader对象上 getResource()
方法源码
classLoader
对象可以从每个类对象获取到,如下:
Main.class.getClassLoader().getResource("data.json")
getResource()
源码:
public URL getResource(String name) {
URL url;
// 双亲委派模型,递归向上请求尝试获取资源
if (parent != null) {
url = parent.getResource(name);
} else {
// 尝试中启动类加载器获取资源
url = getBootstrapResource(name);
}
// 使用当前的 findResource() 获取资源
if (url == null) {
url = findResource(name);
}
return url;
}
可以看到,相交于 class
对象的 classLoader
对象上的 getResource()
方法缺少了 resolveName()
方法,这意味着传入的参数不论是绝对路径还是相对路径,都是以 classpath
为根路径进行查找的。
// 返回 null 因为 data.json 位于 /com/ghimi/demo 下
// 从 classloader getResource 时,会从 classpath 中查找该资源故而查找失败
Main.class.getClassLoader().getResource("data.json");
----------------------------------------
// 返回对应的 url ,因为会拼接 com.ghimi.demo.Main 中的 /com/ghimi/demo到 data.json上
// 长 class getResource时,会从 classpath 中查找 /com/ghimi/demo/data.json 而查找成功
Main.class.getResource("data.json");
当getResource()
方法获取不到资源时,会返回 null
.