Java中取资源时,经常用到Class.getResource和ClassLoader.getResource,这里来看看他们在取资源文件时候的路径问题。
1 :Class.getResource(String path)使用注意事项和示例
path不以’/'开头时,默认是从此类所在的包下取资源;
path 以’/'开头时,则是从ClassPath根下获取;
什么意思呢?看下面这段代码的输出结果就明白了:
package testpackage;
public class TestMain {
public static void main(String[] args) {
System.out.println(TestMain.class.getResource(""));
System.out.println(TestMain.class.getResource("/"));
}
}
输出结果:
file:/E:/workspace/Test/bin/testpackage/
file:/E:/workspace/Test/bin/
上面说到的【path以’/'开头时,则是从ClassPath根下获取;】在这里就是相当于bin目录(Eclipse环境下)。
再来一个实例,假设有如下Project结构:
如果我们想在TestMain.java中分别取到1~3.properties文件,该怎么写路径呢?代码如下:
package testpackage;
public class TestMain {
public static void main(String[] args) {
// 当前类(class)所在的包目录
System.out.println(TestMain.class.getResource(""));
// class path根目录
System.out.println(TestMain.class.getResource("/"));
// TestMain.class在<bin>/testpackage包中
// 2.properties 在<bin>/testpackage包中
System.out.println(TestMain.class.getResource("2.properties"));
// TestMain.class在<bin>/testpackage包中
// 3.properties 在<bin>/testpackage.subpackage包中
System.out.println(TestMain.class.getResource("subpackage/3.properties"));
// TestMain.class在<bin>/testpackage包中
// 1.properties 在bin目录(class根目录)
System.out.println(TestMain.class.getResource("/1.properties"));
}
}
※Class.getResource和Class.getResourceAsStream在使用时,路径选择上是一样的。
2 :Class.getClassLoader().getResource(String path)的使用注意事项和示例
path不能以’/'开头时;path是从ClassPath根下获取;
path 为什么不能以 / 开头,因为classloader 不是用户自定义的类,所以没有相对路径的配置文件可以获取,所以默认都是从哪个classpath 路径下读取,自然就没有必要以 / 开头了。
还是先看一下下面这段代码的输出:
package testpackage;
public class TestMain {
public static void main(String[] args) {
TestMain t = new TestMain();
System.out.println(t.getClass());
System.out.println(t.getClass().getClassLoader());
System.out.println(t.getClass().getClassLoader().getResource(""));
System.out.println(t.getClass().getClassLoader().getResource("/"));//null
}
}
输出结果:
class testpackage.TestMain
sun.misc.Launcher$AppClassLoader@1fb8ee3
file:/E:/workspace/Test/bin/
null
从结果来看【TestMain.class.getResource("/") == t.getClass().getClassLoader().getResource("")】
如果有同样的Project结构
使用Class.getClassLoader().getResource(String path)可以这么写:
package testpackage;
public class TestMain {
public static void main(String[] args) {
TestMain t = new TestMain();
System.out.println(t.getClass().getClassLoader().getResource(""));
System.out.println(t.getClass().getClassLoader().getResource("1.properties"));
System.out.println(t.getClass().getClassLoader().getResource("testpackage/2.properties"));
System.out.println(t.getClass().getClassLoader().getResource("testpackage/subpackage/3.properties"));
}
}
※Class.getClassLoader().getResource和Class.getClassLoader().getResourceAsStream在使用时,路径选择上也是一样的。
3: class.getResource 和classloader.getResource() 原理:
1) class.getResouce 源码:
public java.net.URL getResource(String name) {
name = resolveName(name);//这里返回classpath下的文件地址 ,不包含/
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);//使用类加载器加载配置文件 ,name 是不包含/ 的路径
}
首先看看Class中的resolveName(String name)究竟是干什么的。源码如下所示:
1 private String resolveName(String name) {
2 if (name == null) {
3 return name;
4 }
5 if (!name.startsWith("/")) {
6 Class<?> c = this;
7 while (c.isArray()) {
8 c = c.getComponentType();
9 }
10 String baseName = c.getName(); //返回包括报名在内的类全路径
11 int index = baseName.lastIndexOf('.');
12 if (index != -1) {
13 name = baseName.substring(0, index).replace('.', '/')
14 +"/"+name; //包名+相对路径拼接 刚好拼成classpath下具体文件的地址
15 }
16 } else {
17 name = name.substring(1); //去掉 / 符号,正好是classpath下具体文件的地址
18 }
19 return name;
20 }
下面我把这个源码讲的内容翻译人类语言:
给任意一个字符串name,如果该name是以/开始的,则该函数返回的是:去掉/这个字符的字符串。(如name="/ouyangfeng" 则调用该函数之后得到的结果是:name=ouyangfeng)。
如果该name这个字符串不是以/开始的,则该函数返回的结果是调用这个函数类所在的包名+name组成的字符串(例如假设Test5所在的包名是:com.qls.mount 。则:Test5.class.resolveName("ouyangfeng");返回结果是:com/qls/mount/ouyangfeng)
相当于返回了一个classpath 路径(这个路径是在根下的)
我们看看classloader 的getResource方法: 这个方法的name参数已经是 “包名+文件名” 的格式了
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;
}
这段代码的意思是 先让父类加载器加载资源,如果父类加载器不存在,则使用boot 加载器加载,
如果父类加载器或者根加载器加载失败,则使用当前加载器加载配置文件。
protected URL findResource(String name) {
return null;
}
这里的方法返回空,并且方法是protected 的,所以自定义类加载器的话,需要覆盖这个方法,
并且 系统类加载器和扩展类加载器肯定已经实现了这个方法,分别用来加载 用户自定义程序中classpath 路径下的文件 和 /lib/ext 下jar包内的配置文件
2 )Class类中的getResourceAsStream(String name)的源码如下:
1 public InputStream getResourceAsStream(String name) {
2 name = resolveName(name);//注意这里有一个resolveName(String name)方法,根据上述的分析,易知道这个源码的意思.
3 ClassLoader cl = getClassLoader0();
4 if (cl==null) {
5 // A system class.
6 return ClassLoader.getSystemResourceAsStream(name);
7 }
8 return cl.getResourceAsStream(name);
9 }
getClassLoader0() 这依据啥意思不太懂。
其中 ClassLoader中的getResourceAsStream(String name)中的源码如下:
1 public InputStream getResourceAsStream(String name) {
2 URL url = getResource(name);
3 try {
4 return url != null ? url.openStream() : null;//这句代码的意思是:如果url不是null时返回的是:url.openStream(),反之如果url为null则返回null.
5 } catch (IOException e) {
6 return null;
7 }
8 }
ClassLoader.getResourceAsStream(fileName)获取的流对象:
如果filename 是本工程文件 :java.io.bufferedInputStream
如果file是jar包中文件:sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream
Java 应用读取 jar 包中的文件https://blog.csdn.net/antony1776/article/details/89249509