博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
说说资源加载的问题(Class的getResource方法)
阅读量:6957 次
发布时间:2019-06-27

本文共 8481 字,大约阅读时间需要 28 分钟。

背景

最近的项目中又碰到了这个问题。需要在web应用的指定路径去手工加载一个文件。

平时当项目中某些地方需要手工加载文件的时候,要么
xxx.class.getResource, 要么xxx.getClass.getClassLoader.getResource,如果两个都不好使,我通常就是会使用一些比较猥琐的办法或是想其他办法规避掉这个加载问题,今天正好有空,是时候来仔细分析下了。

熟悉的双亲委派原则

先来看一张图,对于java的开发来说,熟悉的不能再熟悉了。

  • Bootstrap ClassLoader:加载诸如rt.jar等核心文件
  • Extension ClassLoader:加载ext目录下的扩展文件
  • System ClassLoader:习惯性也称之为AppClassLoader。加载用户的classpath下的文件。
  • User-Defined ClassLoader:用户自定义类加载器。

类加载器寻找的文件路径

上一节我们知道了各个类加载器所负责的功能,可是我们应该知道的更细节一些,每个类加载器到底是去哪些目录查找文件的呢?

BootstrapClassLoader

BootstapClassLoader要找的路径如下,代码见sun.misc.Launcher.BootClassPathHolder:

再往上翻,可以看到bootClassPath的来源:

我们可以清楚的看到,BootstrapClassLoader是从System.getProperty("sun.boot.class.path")来获取查找路径的。在我本机,获得的URL列表如下:

D:\programme\jdk\jdk8U74\jre\lib\resources.jar;D:\programme\jdk\jdk8U74\jre\lib\rt.jar;D:\programme\jdk\jdk8U74\jre\lib\sunrsasign.jar;D:\programme\jdk\jdk8U74\jre\lib\jsse.jar;D:\programme\jdk\jdk8U74\jre\lib\jce.jar;D:\programme\jdk\jdk8U74\jre\lib\charsets.jar;D:\programme\jdk\jdk8U74\jre\lib\jfr.jar;D:\programme\jdk\jdk8U74\jre\classes复制代码

ExtClassLoader

获取ExtClassLoader装载的目录如下, 代码见sun.misc.Launcher.ExtClassLoader:

我们可以清楚的看到,ExtClassLoader是从System.getProperty("java.ext.dirs")来获取查找路径的。在我本机,获得的URL列表如下:

D:\programme\jdk\jdk8U74\jre\lib\ext;C:\windows\Sun\Java\lib\ext 复制代码

AppClassLoader

能看到直接调用new AppClassLoader()的地方就一处, 代码见sun.misc.Launcher.AppClassLoader:

很清楚的看到, AppClassLoader是直接获取了当前系统的java.class.path属性。在我的本机上,获得的URL列表如下:

D:\programme\jdk\jdk8U74\jre\lib\charsets.jar;D:\programme\jdk\jdk8U74\jre\lib\deploy.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\access-bridge-64.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\cldrdata.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\dnsns.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\jaccess.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\jfxrt.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\localedata.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\nashorn.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\sunec.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\sunjce_provider.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\sunmscapi.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\sunpkcs11.jar;D:\programme\jdk\jdk8U74\jre\lib\ext\zipfs.jar;D:\programme\jdk\jdk8U74\jre\lib\javaws.jar;D:\programme\jdk\jdk8U74\jre\lib\jce.jar;D:\programme\jdk\jdk8U74\jre\lib\jfr.jar;D:\programme\jdk\jdk8U74\jre\lib\jfxswt.jar;D:\programme\jdk\jdk8U74\jre\lib\jsse.jar;D:\programme\jdk\jdk8U74\jre\lib\management-agent.jar;D:\programme\jdk\jdk8U74\jre\lib\plugin.jar;D:\programme\jdk\jdk8U74\jre\lib\resources.jar;D:\programme\jdk\jdk8U74\jre\lib\rt.jar;D:\alibaba\content-alibaba\tinker\target\classes;D:\programme\opensource\maven\repository\commons-digester\commons-digester\1.7\commons-digester-1.7.jar;...maven...D:\programme\opensource\maven\repository\commons-beanutils\commons-beanutils\1.6\commons-beanutils-1.6.jar;D:\programme\opensource\maven\repository\commons-logging\commons-logging\1.0\commons-logging-1.0.jar; D:\programme\IntelliJ IDEA Community Edition 2016.1.1\lib\idea_rt.jar复制代码

我们可以关注到以下细节:

  1. 路径列表中包含了ext扩展jar目录。
  2. 应用目录的class目录被包含D:\alibaba\content-alibaba\tinker\target\classes;
  3. maven目录被包含
  4. idea的一个文件idea_rt.jar被包含(这是因为idea启动java程序时默认会在-classpath中带上这个jar)

文件查找过程之getClass().getClassLoader().getResource("data/resource.xml")

getClass().getClassLoader():一般来说我们在应用里边使用的话获得的ClassLoader都是AppClassLoader或者用户自定义ClassLoader。

getResource()这是关键的方法。

熟悉的同学明白,这依然是一个双亲委派的资源寻找过程, 假如一个文件在classpath下,基本的寻找过程如下:

AppClassLoader --> ExtClassLoader --> BootstrapClassLoader --> ExtClassLoader --> AppClassLoader

接下来看具体寻找文件的过程sun.misc.URLClassPath:

两个关键点:

  • getNextLoader 主要获取当前URL查找项对应的Loader。大部分情况下,每个URL都可以归类为FileLoader和JarLoader。分别代表当前查找URL是一个文件夹或是一个jar包。

  • loader.getResource

先来看看jar包是如何找文件的,见sun.misc.URLClassPath.JarLoader:

核心就是使用JarEntry.getJarEntry方法。该方法可以在jar中查找对应文件是否存在。举个栗子:

/** * 2017/2/14 11:13 by 热海 */public class JarFileTest {    public static void main(String[] args) throws Exception{        JarFile jar = new JarFile("D:\\programme\\opensource\\maven\\repository\\com\\alibaba\\citrus\\citrus-webx-all-in-one\\3.0.6\\citrus-webx-all-in-one-3.0.6.jar");        JarEntry entry = jar.getJarEntry("META-INF/services-data-resolver-factories.bean-definition-parsers");        System.out.print(entry);    }}复制代码

以上代码就是判断META-INF/services-data-resolver-factories.bean-definition-parsers这个路径下的文件在citrus-webx-all-in-one-3.0.6.jar中是否存在。

再来看看对于文件夹FileLoader, 看看是如何寻找文件的,见sun.misc.URLClassPath.FileLoader:

能看到是文件路径拼接。也就是当前查找的URL路径 + 资源文件路径的拼接。

文件查找过程之getClass().getClassLoader().getResource("/data/resource.xml")

上一节我们看了getClassLoader().getResource("data/resource.xml"), 这一节我们看下

getClassLoader().getResource("/data/resource.xml")。

我们现在已经知道,有两种Loader,一种专门负责从jar中加载资源(JarLoader),一种负责从文件夹中加载资源(FileLoader)。先来看看FileLoader:

由于原始的参数是/data/resource.xml, 是个绝对的路径,所以和baseUrl进行组装是失败,获得的url依然是/data/resource.xml。此时继续代码走入了异常流,因为代码强制要求最终的url要以baseUrl开头。所以此路不通。

再看JarLoader:

JarEnty的查询是不接受绝对路径的name的,还是之前那个JarEntry的栗子,我稍微调整下:

正常情况下就是没问题的:

所以绝对路径查找在此方法中行不通。

文件查找过程之getClass().getResource("data/resource.xml")

我们再变一下,现在去掉getClassLoader(), 直接getResource获取文件看看是什么结果。

直接获取resource的时候,相比之前,多了一个很重要的方法,见java.lang.Class:

我们可以看到,之后也是使用当前类的类加载器去寻找资源,这样就和上边调用getClassLoader没什么两样了!所以关键点在于,这个resolveName方法做了什么事情,继续见java.lang.Class:

方法很简单。当碰到"/"不是第一个字符的资源是,直接包装路径为当前类的同目录下资源。假设当前类是

com.taobao.shopbase.Index想要加载data/resource.xml这个资源,则会把资源路径解析为
com/taobao/shopbase/data/resource.xml。

文件查找过程之getClass().getResource("/data/resource.xml")

继续。

可以看到当查找的资源首字符是"/"时。直接是取后边的部分然后再调用getResource。所以这个小标题的代码可以写成getClass().getClassLoader().getResource("data/resource.xml"), 等于说又是一个在classpath中寻找相对资源路径的问题。

meta-index文件,空间换时间

想象这样一个场景。我想要在bootClassLoader中进行查找一个tmp.log的文件是否存在这些jar包中。

如果每次都来一个进行查询的话。不是不可以,但是感觉总是很累(我本地的JDK版本1.8.0_91,rt.jar已经超过60M了)。并且我们的JDK是一个固定的版本在哪里,JDK中的jar也不会发生变化。

有没有办法呢?来看meta-index文件。在我的本机,位置在:file:///D:/programme/jdk/jdk8U74/jre/lib/meta-index, 文件内容如下:

% VERSION 2% WARNING: this file is auto-generated; do not edit% UNSUPPORTED: this file and its format may change and/or%   may be removed in a future release# charsets.jarsun/niosun/awt# jce.jarjavax/cryptosun/securityMETA-INF/ORACLE_J.RSAMETA-INF/ORACLE_J.SF# jfr.jaroracle/jrockit/jdk/jfrcom/oracle/jrockit/! jsse.jarsun/securitycom/sun/net/! management-agent.jar@ resources.jarcom/sun/java/util/jar/pack/META-INF/services/sun.util.spi.XmlPropertiesProviderMETA-INF/services/javax.print.PrintServiceLookupcom/sun/corba/META-INF/services/javax.sound.midi.spi.SoundbankReadersun/printMETA-INF/services/javax.sound.midi.spi.MidiFileReaderMETA-INF/services/sun.java2d.cmm.CMMServiceProvidejavax/swingMETA-INF/services/javax.sound.sampled.spi.AudioFileReaderMETA-INF/services/javax.sound.midi.spi.MidiDeviceProvidersun/netMETA-INF/services/javax.sound.sampled.spi.AudioFileWritercom/sun/imageio/META-INF/services/sun.java2d.pipe.RenderingEngineMETA-INF/mimetypes.defaultMETA-INF/services/javax.sound.midi.spi.MidiFileWritersun/rmijavax/sqlMETA-INF/services/com.sun.tools.internal.ws.wscompile.Plugincom/sun/rowset/META-INF/services/javax.print.StreamPrintServiceFactoryMETA-INF/mailcap.defaultjava/langsun/textjavax/xmlMETA-INF/services/javax.sound.sampled.spi.MixerProvidercom/sun/xml/META-INF/services/com.sun.tools.internal.xjc.Plugincom/sun/java/swing/com/sun/jndi/com/sun/org/META-INF/services/javax.sound.sampled.spi.FormatConversionProvider! rt.jarcom/sun/java/util/jar/pack/java/org/ietf/com/sun/beans/com/sun/tracing/com/sun/java/browser/com/sun/corba/com/sun/media/com/sun/awt/com/sun/management/sun/com/sun/jmxcom/sun/demo/com/sun/imageio/com/sun/net/com/sun/rmi/org/w3c/com/sun/swing/com/sun/activation/com/sun/nio/com/sun/rowset/org/jcp/com/sun/istack/jdk/com/sun/naming/org/xml/org/omg/com/sun/security/com/sun/image/com/sun/xml/com/sun/java/swing/com/oracle/com/sun/java_cup/com/sun/jndi/com/sun/accessibility/com/sun/org/javax/  复制代码

简单来说, 这个文件告诉BootClassLoader, 针对某个jar包,哪些目录下是纯class文件。 同时也透出了当前jar包的目录情况。这样在判断一个普通文件是否在JDK包时,只需和meta-index比对就好了,会极大的节省时间。具体文件分析过程见sun.misc.MetaIndex。

综述

BB了这么多,简单的总结一下。

  1. getResource方法不支持操作系统绝对路径。资源的寻找都是基于某个绝对的地址开始相对的查找。
  2. 资源的查找也是一个双亲委派的查找过程
  3. BootstrapClassLoader的查找路径来自于系统的java.class.path属性。
  4. ExtClassLoader的查找路径来自于系统的java.ext.dirs属性。
  5. AppClassLoader的查找路径来自于系统的java.class.path属性。
  6. BootstrapClassLoader的meta-index的方案值得学习。
  7. 可以利用JarFile和JarEntry来获取某个文件是否在jar包中。注意,参数路径要是相对的。
  8. getClass().getClassLoader().getResource("data/resource.xml")从classpath遍历
  9. getClass().getClassLoader().getResource("/data/resource.xml")此路不通
  10. getClass().getResource("data/resource.xml")是以当前类为基准做相对路径查找
  11. getClass().getResource("/data/resource.xml")功能等同于第八条。

如果有理解不对的地方,欢迎各位指正。

转载地址:http://hmmil.baihongyu.com/

你可能感兴趣的文章
如果centos7添加新网卡,系统不识别的解决办法
查看>>
JMX监控zookeeper
查看>>
Anaconda 2019.03 发布,Python 跨平台科学计算软件
查看>>
JS中遍历语法的比较
查看>>
读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
查看>>
《C++ Primer》学习笔记:3.3.3其他vector操作
查看>>
NetBeans的(默认)快捷键
查看>>
年薪1美金还打N份工,我的收入终于超过马云了
查看>>
中国实体新气象:失之电商,收之“+互联网”
查看>>
W3C 宣布:WebAuthn 成为正式 Web 标准
查看>>
智能机器人登陆eSmart!或制造玩具市场新“痛点”
查看>>
调查显示,大多数 Java 开发人员不希望学习新语言
查看>>
想练球却没人怎么办?找Trainerbot乒乓球机器人!
查看>>
融入CMMI管理思想的计算机化系统验证
查看>>
eclipse导入第三方jar包进入web项目的方法
查看>>
发展至今的机器学习到底对我们的就业和社会产生了哪些影响?
查看>>
2017四川电商年度盛典,千机网论道企业变革
查看>>
Ubuntu 16.04安装QQ(不一定成功)
查看>>
四种方法教你破解Linux(CentOS7.4)系统的root密码
查看>>
阿里云郑晓:浅谈GPU虚拟化技术(第一章)
查看>>