getResource是Class类中的一个方法
作用呢就是配置文件的读取
首先我们得知道,在new一个对象的时候会在java堆区中生成一个代表这个类的java.lang.Class对象,(不能主动创建,只能获取)作为这个类的各种数据的访问入口
我们怎样使用对应的Class对象呢?
三种方法 ,先看看我的目录结构
Resource.class //直接使用类名.class
Class.forName("zhang.Resource") //包名.类名 需要捕获异常
new Resource().getClass() //先获取一个对象 再使用getCLass()方法
现在我们写五个语句,前两个没/
后三个有/
System.out.println(Resource.class.getResource(""));
System.out.println(Resource.class.getResource("Resource.class"));
System.out.println(Resource.class.getResource("/"));
System.out.println(Resource.class.getResource("/Resource.class"));
System.out.println(Resource.class.getResource("/zhang/Resource.class"));
返回的地址都是绝对地址
file:/C:/Users/java/target//target/classes/zhang/
file:/C:/Users/java/target//target/classes/zhang/Resource.
file:/C:/Users/java/target//target/classes/
null
file:/C:/Users/java/target//target/classes/zhang/Resource.class
现在我们来分析 按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(想要加载的文件的相对地址)被resolveName()方法调用并再次赋值给了name
所以我们要分析下resolveName这个方法
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) { //不以”/”开头
Class<?> c = this; //得到 class zhang.Resource
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName(); //得到 zhang.Resource 也就是去掉了class
/* 来看下getName()的源码
public String getName() {
String name = this.name; //这里的this.name 就是 zhang.Resource
if (name == null)
this.name = name = getName0();
return name;
}
*/
int index = baseName.lastIndexOf('.'); //返回最后一次出现.位置的索引
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/') //拼接 zhang/想要加载的文件的相对地址
+"/"+name;
}
} else {
name = name.substring(1); //以“/”开头 去掉“/” 所以得到的是 /classes/想要加载的文件的相对地址
}
return name; //返回的name都是去掉"/"的
}
通过这个方法,我们可以看出加/
比不加/
多加了个zhang/的目录(通过Class> c = this;
然后c.getName()
),也就是包名的目录
所以
加了/ :classpath根目录下 /classes/
没加/ :字节码对象所在目录下 /classes/zhang/ (多了个zhang/)
classpath: 指的是编译后的class文件、xml、properties等配置文件所在的目录 如下图
加了/
就是classes下面的 ,没加/
就是classes/zhang下面的
接下来再来看看后面的代码
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 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多了个resolveName方法
Class.getResource最后返回的name都是去掉/
的 也就是说你使用ClassLoader.getResource方法时候加了/
最后只会返回null
参考
彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理
java有几种类加载器?工作原理是什么?