getResource 的作用
getResource
为Class类
中的一个方法
作用:配置文件的读取
Class 的概念
首先我们得知道,在new一个对象
的时候,会在java堆中
生成一个代表这个类的java.lang.Class对象
,(Class 对象不能主动创建,只能获取)作为该类的数据访问入口
那么我们如何使用对应的Class对象呢?
三种方法 ,先看看我的目录结构
Resource.class //直接使用类名.class
Class.forName("zhang.Resource") //包名.类名 需要捕获异常
new Resource().getClass() //先获取一个对象 再调用对象的 getClass()方法
现在我们写6个语句,前3个没/
,后3个有/
(强烈建议自己试一试)
public class Resource {
public static void main(String[] args) throws IOException {
System.out.println("------------------------------getResource(\"\")测试---------------------");
// 1 返回 target/classes/package
System.out.println(Resource.class.getResource(""));
// 2 同级(编译路径)下,是否存在对应文件,存在则返回该地址
System.out.println(Resource.class.getResource("Resource.class"));
// 3 不存在,返回 null
System.out.println(Resource.class.getResource("NoResource.class") + "\n\n");
System.out.println("------------------------------getResource(\"/\")测试---------------------");
// 4 返回 target/classes/
System.out.println(Resource.class.getResource("/"));
// 5 target/classes/ 目录下不存在 Resource.class
System.out.println(Resource.class.getResource("/Resource.class"));
// 6 target/classes/ 目录下存在 cn/zhang
System.out.println(Resource.class.getResource("/cn/zhang"));
}
}
结果如下,返回的地址都是绝对地址
------------------------------getResource("")测试---------------------
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/cn/zhang/springboot3sourcelearn/demo/
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/cn/zhang/springboot3sourcelearn/demo/Resource.class
null
------------------------------getResource("/")测试---------------------
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/
null
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/cn/zhang
区别如下
- 无
/
:target/classes/package
- 有
/
:target/classes/
/
,有点类似于 linux 中的/
—— 根路径 (而无/
相当于相对路径)
getResource 源码分析
现在我们来分析:按ctrl + 鼠标左键,点进去查看源码
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);
}
对传入的name
(想要加载的文件的相对地址
)进行解析,并再次赋值给name
继续分析resolveName方法
resolveName 方法(JDK17)
private String resolveName(String name) {
if (!name.startsWith("/")) {
String baseName = getPackageName();
if (!baseName.isEmpty()) {
int len = baseName.length() + 1 + name.length();
StringBuilder sb = new StringBuilder(len);
name = sb.append(baseName.replace('.', '/')) // <---- package 中的 . 替换为 /
.append('/')
.append(name) // <----- package/name
.toString();
}
} else {
name = name.substring(1); // 以 "/" 开头,去掉 "/"
}
return name; // 最后的返回结果都是去掉"/"的
}
验证了之前的结论,不加 / ,会 append getPackageName,即:以当前类所在的 package 为基准
classpath: 指的是编译后的class文件、xml、properties等配置文件
所在的目录,如下图
因此,""
为相对地址
,"/"
为绝对地址
getResource 之后的代码
接下来再来看看后面的代码
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0(); // 获取加载该 Class 的 ClassLoader
if (cl==null) { // 如果加载该 Class 的 ClassLoader 为 null,则表示这是一个系统 class
// A system class. 系统类加载器
return ClassLoader.getSystemResource(name); // 调用 ClassLoader 的 getSystemResource 方法
}
return cl.getResource(name); // 调用 ClassLoader 的 getResource 方法
}
getResource
最终调用的是ClassLoader
的getSystemResource方法
或者getResource方法
现在来分析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); // <----- return null
}
return url;
}
这里涉及到一个知识点 :双亲委派机制
就是如果一个类加载器(用来加载 class 文件)收到了类加载的请求,首先不会自己尝试去加载这个类,而是将请求委派给父类加载器
去完成
只有当父类加载器无返完成这个加载请求,子加载起才会尝试自己去完成加载
Java中的类加载器主要分为以下四类:
(1)根类加载器(BootStrapClassLoader), 主要负责加载jre/lib/rt.jar相关的字节码文件的。
(2)扩展类加载器(ExtensionClassLoader), 主要负载加载 jre/lib/ext/*.jar 这些jar包的。 该类加载器在JDK1。9的时候更名为: Platform Class Loader, 其父类加载器为: null。
(3)应用程序类加载器(ApplicationClassLoader), 主要负责加载用户自定义的类以及classpath环境变量所配置的jar包的。 该类加载器在JDK1.9的时候更名为: System ClassLoader, 其父类加载器为: ExtensionClassLoader。
(4)自定义类加载器(UserClassLoader), 负责加载程序员指定的特殊目录下的字节码文件的。大多数情况下,自定义类加载器只需要继承ClassLoader这个抽象类,重写findClass()和loadClass()两个方法即可。
Class.getResource
与ClassLoader.getResource
的区别
Class.getResource
:对传入的 name
进行了处理(多了resolveName方法
)
Class.getResource
最后返回的name
都是去掉/
的- 无
/
会添加package
ClassLoader.getResource方法
,只能处理无 /
的路径,如果一开始加了 /
,返回结果为 null
System.out.println(Resource.class.getResource(""));
System.out.println(Resource.class.getResource("/"));
System.out.println(ClassLoader.getSystemResource(""));
System.out.println(ClassLoader.getSystemResource("/"));
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/cn/zhang/springboot3sourcelearn/demo/
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/
file:/E:/zy-source-learn/spring-family/springboot3-source-learn/target/classes/
null
参考
彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理
java有几种类加载器?工作原理是什么?