一文搞懂class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别和联系
声明:本文转载,原文链接:
一文搞懂class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别和联系 - 简书 https://www.jianshu.com/p/e3c94eff77c5
前言
我们在阅读一些开源框架的时候,会发现有些开源框架在读取配置文件采用的是class.getClassLoader().getResource("xx.xml")这种方式,还有一些采用的是class.getResource("xx.xml")。那么此时我们可能就产生了一些疑问,这两种读取资源的方式有什么不同呢?现在我们就解析一下相关的源码,看看他们之间的区别和联系。
类加载器科普
我们知道JVM的类加载器是分为四大块的,分别是BootStrapClassLoader,extClassLoader以及AppClassLoader,最后就是我们自己自定义的ClassLoader。
他们之间的父子关系通过下面的代码可以进行验证(自定义类加载器的父加载器是AppClassLoader,由于自定义类加载器不是本文的重点,就不加以验证,感兴趣的小伙伴可以自行验证)
public class ResourceTest { public static void main(String[] args) { ClassLoader classLoader = ResourceTest.class.getClassLoader(); //通过返回结果可以看到自己写的类默认是由applicationClassLoader加载的 System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //类加载器是有父子关系的,appClassLoader的父加载器是extClassLoader classLoader = classLoader.getParent(); System.out.println(classLoader);//sun.misc.Launcher$ExtClassLoader@61bbe9ba //类加载器是有父子关系的,extClassLoader的父加载器是BootStrapClassLoader,但是BootStrapClassLoader是C语言写的,所以会返回null classLoader = classLoader.getParent(); System.out.println(classLoader);//null } }
这时候我们可能就会有一个疑问,这些不同的类加载器都是加载哪些java文件呢?
其实这些在sun.miscLauncher类里就有答案,现在我们打开这个类来看下。
1.bootStrapClassLoader
上图中红色箭头指定的路径就是bootstrapClassLoader类加载器可以加载的类的路径
我们通过代码演示一下都有哪些类是属于bootStrapClassLoader来加载的
public class ClassLoaderTest { public static void main(String[] args) { String str = System.getProperty("sun.boot.class.path"); Arrays.stream(str.split(";")).forEach(s -> System.out.println(s)); /* C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\sunrsasign.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar C:\Program Files\Java\jdk1.8.0_91\jre\classes */ } }
2.extClassLoader
上面红色箭头指定的路径就是extClassLoader加载的路径,现在我们同样通过代码来看看这个类加载器主要加载哪些类。
import java.util.Arrays; public class ClassLoaderTest { public static void main(String[] args) { String str = System.getProperty("java.ext.dirs"); Arrays.stream(str.split(";")).forEach(s -> System.out.println(s)); /* C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext C:\WINDOWS\Sun\Java\lib\ext */ } }
3.appClassLoader
import java.util.Arrays; public class ClassLoaderTest { public static void main(String[] args) { String str = System.getProperty("java.class.path"); Arrays.stream(str.split(";")).forEach(s -> System.out.println(s)); /* C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\deploy.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-64.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\javaws.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfxswt.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\management-agent.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\plugin.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar G:\code\SelfStudy\jdk_study\out\test\jdk_study G:\code\SelfStudy\jdk_study\out\production\jdk_study C:\Users\闫尚\.m2\repository\junit\junit\4.12\junit-4.12.jar C:\Users\闫尚\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar C:\Program Files\Java\jdk1.8.0_91\lib\dt.jar C:\Program Files\Java\jdk1.8.0_91\lib\tools.jar C:\Program Files\Java\jdk1.8.0_91\lib\sa-jdi.jar C:\Program Files\Java\jdk1.8.0_91\lib\jconsole.jar C:\Program Files\Java\jdk1.8.0_91\lib\packager.jar C:\Program Files\Java\jdk1.8.0_91\lib\javafx-mx.jar C:\Program Files\Java\jdk1.8.0_91\lib\ant-javafx.jar G:\soft\idea\lib\idea_rt.jar */ } }
验证环境
(注意:这里主要是针对自定义类做的测试,自定义类默认的类加载器是appClassLoader,如果你用String等JDK自带的类做测试的话,因为所属的类加载器不同,debug的结果也是不同的,请自行验证)
import java.net.URL; public class ResourceTest { public static void main(String[] args) { URL url = ResourceTest.class.getClassLoader().getResource("application.xml"); System.out.println(url); url = ResourceTest.class.getResource("application.xml"); System.out.println(url); /* * 结果; file:/G:/code/SelfStudy/jdk_study/out/production/jdk_study/application.xml file:/G:/code/SelfStudy/jdk_study/out/production/jdk_study/application.xml */ } }
源码分析
有了上面所科普的类加载器的相关知识,那么我们就以验证环境的代码来看看class.getClassLoader().getResource("application.xml")和class.getResource("application.xml")的区别到底在哪。
1.分析class.getClassLoader().getResource("application.xml")的源码
public URL getResource(String name) { //递归调用 URL url; if (parent != null) { url = parent.getResource(name); } else {//递归调用获取BootStrapClassLoader的URL url = getBootstrapResource(name); } if (url == null) { url = findResource(name); } return url; }
private static URL getBootstrapResource(String name) { URLClassPath ucp = getBootstrapClassPath(); Resource res = ucp.getResource(name); return res != null ? res.getURL() : null; }
getBootstrapResource这个方法又调用了getBootstrapClassPath()去获取路径,那么getBootstrapClassPath()是去哪里拿的路径呢?
// Returns the URLClassPath that is used for finding system resources. static URLClassPath getBootstrapClassPath() { return sun.misc.Launcher.getBootstrapClassPath(); }
看到sun.misc.Launcher.getBootstrapClassPath() 是不是豁然开朗?这就是我们前面分析的bootStrapClassLoader加载的路径啊!
OK,现在我们得出第一个结论:getClassLoader().getResource("application.xml")首先会去根类加载器加载的路径下找application.xml文件。如果找到就直接返回application.xml对应的URL地址
通过debug的结果我们发现如果bootStrapClassLoader加载的路径下没有找到application.xml就会去extClassLoader类加载器加载的路径下去找。
OK,这里我们得出第二个结论:
getClassLoader().getResource("application.xml")在根类加载器加载路径下找不到application.xml时就会去extClassLoader加载器加载的路径去找。
OK,此时我们得出第三个结论:
getClassLoader().getResource("application.xml")在extClassLoader加载器加载路径下找不到application.xml时就会去appClassLoader加载器加载的路径去找。如果appClassLoader加载路径下再找不到,直接返回null。
2.分析class.getResource("application.xml")的源码
public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0();//获取当前类的类加载器 if (cl==null) {//bootStrapClassLoader // A system class. return ClassLoader.getSystemResource(name); } return cl.getResource(name);//调用的还是class.getClassLoader.getResouce(name) }
private String resolveName(String name) { if (name == null) {//1.name = null 直接返回 return name; } if (!name.startsWith("/")) {//2.1 name不以/开头 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 { //2.2 name以/开头 就截取/之后的字符串 name = name.substring(1); } return name; }
上面的代码一目了然,就是对name进行一系列的组装,组装完成后还是调用的class.getClassLoader().getResource(name)方法去查找对应的资源文件。
OK,此时我们得出结论:
通过resolveName(name)组装后得到的name参数值跟class.getClassLoader().getResource(name)中的name参数值一样的话,他们最后的输出结果没有任何区别。
结束语
通过源码的分析,我们就大概知道了class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别以及联系,因为时间有限,就不对其他情况进行分析验证了,大家可以通过上面源码分析的思路去自己验证,谢谢大家。
作者:负重前行丶
链接:https://www.jianshu.com/p/e3c94eff77c5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
本文转载,原文链接:
一文搞懂class.getClassLoader().getResource("xx.xml")和class.getResource("xx.xml")的区别和联系 - 简书 https://www.jianshu.com/p/e3c94eff77c5