笔者最近在一个小测试中遇到了关于getClass().getResource("")和getClass().getClassLoader().getResource("")一个小问题,因为之前对这两个方法理解都不是很深刻,遇到这样的问题总是很抓狂,因此,趁着这次机会好好的研究了一下他们的工作原理,并且记录在这里,方便自己以及有需要的人查阅。
背景
问题的起因是笔者的小测试中引入了部门中的一个第三方包,而这个包中有一行代码跑出了NullPointerException. 具体原因是它使用了getClass().getClassLoader().getResource("")想要获取当前项目的运行目录。而getClass().getClassLoader().getResource("")的结果是null。当笔者遇到这个问题的时候也很懵逼。随即各种尝试,最后使用getClass().getResource("")方法解决了问题。但不明真相。
所以笔者带着两个问题开始进行研究:
1. 为什么getClass().getClassLoader().getResource("")返回的结果是Null
2. getClass().getResource("") 与 getClass().getClassLoader().getResource("") 有什么区别。
Round1
需要注意的是,笔者出问题的小测试是以jar包的形式运行在jvm里的。所以笔者先写了一段小程序。
然后以jar包的形式运行,得到结果如下,和笔者遇到的问题一致。
而当笔者通过在IDE里直接运行下面的程序,结果是OK的。
这里可以发现,貌似使用jar包运行程序与在IDE里面直接运行程序是有差异的。同时也发现,在以jar包的形式运行的时候,getClass().getClassLoader().getResource("") 返回了Null, 而getClass().getResource("") 返回的是一个jar包的路径。
Round2
那么现在可以开始debug了,看一下到底底层做了什么使两种形式返回的结果不一样。首先,需要下载JDK的源码,由于Oracle的JDK只能获取java和javax包下的源码,不全,所以笔者下载了openJDK8的源码。
根据debug的结果,笔者发现当JVM开始运行,会加载classpath下的class文件以及jar包。 在类URLClassPath里有个内部类叫Loader,而这个类就是专门负责加载class文件及jar包的。Loader与ClassLoader的关系可以简单的理解为每一个ClassLoader对应多个Loader。而到底需要多少个Loader来加载文件及jar包是需要根据参数URLs来决定的。用一张图来表示就是
但是Loader它还有两个子类,一个叫FileLoader,另外一个叫JarLoader,不难看出,如果是加载jar文件,那么应该就是用JarLoader来加载了,而如果是class文件那么就应该是用FileLoader来加载了。因而,上面的关系图可以修改为
当调用getClass().getClassLoader().getResource("") 时,其实就是在遍历所有的JarLoader和FileLoader,看有没有相匹配的路径。很明显,笔者遇到的在执行jar文件和IDE里得到不同的结果与JarLoader和FileLoader的实现有直接的关系。
JarLoader是将jar文件加载并将里面的class文件都加载到内存中,形成相对应的一个叫JarEntry的类。所以当执行getClass().getClassLoader().getResource("")时,没有找到与空字符串想匹配的的JarEntry,最终返回null。
FileLoader则不同,它首先知道当前的ClassPath, 所以初始化了一个File(classPath)对象。所以当执行
getClass().getClassLoader().getResource(path)时,最终返回的是File.getPath+"/"+path,既当path为空字符串时,返回的是classPath路径。
到这里,第一个问题基本上解决了。第二个问题网上的回答也比较多,其实从源码可以轻松的找到答案。
getClass().getResource("") 最终也是调ClassLoader.getResource()方法,只不过它多了一步,就是解析传入的name。
当name为空时,返回的就是当前class所在的路径,在笔者的测试环境中就是“com.test”。所以,当调用getClass().getResource("")时就是调用
getClass().getClassLoader().getResource("com/test/")。这也是为什么笔者开始歪打正着的用getClass().getResource("")解决了这个问题。
建议大家再看看源码,动手debug一下,会发现很有意思。