1.前言

为什么要写这篇文章?身为Java程序员你有没有过每次需要读取 ClassPath 下的资源文件的时候,都要去百度一下,然后看到下面的这种答案:

Thread.currentThread().getContextClassLoader().getResource("ss.properties").getPath();

亦或是:

Object.class.getResourceAsStream("ss.properties");

你复制粘贴一下然后放到自己的项目里运行,还真跑起来了。但是当打成 jar 包作为其它项目的依赖时,或者打成 war 包被 Tomcat 加载时,你还能保证你的resources 资源文件被读取到吗?答案是不能的。

其中的原因如何而又如何解决,究竟怎样才能写出万无一失根本不用担心任何环境的代码?个中原委,请听我一一道来。

2.再看类加载机制

看到这个标题你也许会有些意外,不是说的读取ClassPath下的文件吗?为什么要讲类加载机制。

其实你有没有想过,ClassPath下的资源文件标准存放的是什么?顾名思义,是 .class 类文件。为什么我们的类可以被正确加载到Java虚拟机(JVM),而自己添加的资源文件却加载失败呢?归根结底是你没有理解类加载机制,也就无法做到举一反三。

类加载机制与类加载器

程序员将源代码写入.Java文件中,经过(javac)编译,生成.class二进制文件。虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

从宏观上理解了类加载机制后,接下来就要从细节上说一说类加载器,以及类加载器的工作原理。

类加载器,顾名思义,是加载类的器件。JVM只存在两种不同的类加载器:启动类加载器(Bootstrap ClassLoader),使用C++实现,是虚拟机自身的一部分。另一种是所有其他的类加载器,使用JAVA实现,独立于JVM,并且全部继承自抽象类java.lang.ClassLoader。包括扩展类加载器、应用程序类加载器。

它我们在写代码时,总是会new很多对象,我们之所以可以new出对象,是因为该对象对应的类已经被JVM加载为Class类的对象实例。这句话有点绕,我用代码展示一下:

Obj obj = new Obj(); //Obj对象实例
Class o = obj.getClass(); //Obj类是Class类的对象实例

在JVM中,一般情况下,我们的类的类实例是唯一的,这得益于类加载机制的双亲委派模型。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都是应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

3.类也是一种Resource

言归正传,通过上述对类加载机制的学习,我们可以得出这样的一个结论:一个类文件是由某个类加载器负责加载到JVM中的,且只会有一个类加载器去加载。反过来说,由一个类实例就可以获取到加载它到JVM中的那个类加载器。

用代码阐述我的上段话如下所示:

Obj obj = new Obj();
ClassLoader classLoader = obj.getClass().getClassLoader();

跟着我的思路继续走,该类加载器之所以可以加载这个类,是因为这个类在该类加载器的搜索范围内。类加载器既然可以加载这个类文件,那么也可以加载该类文件同级目录下的所有资源文件。

所以,我们要想确保可以读取到某个资源文件,只需调用和该资源文件在同一目录下的类的Class对象的getClassLoader()方法获取该类加载器即可

举个例子,我们有一个properties文件和Obj.class在同一个目录下, 那我们读取该properties文件的最正确的方式就是通过Obj.class.getClassLoader().getResourceAsStream()方法。

4.一个错误的例子

为了印证上面的结论,先看下 Object.class.getResourceAsStream() 的源码:

// Class.java
public InputStream getResourceAsStream(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResourceAsStream(name);
    }
    return cl.getResourceAsStream(name);
}

从 Javadoc 文档和源码中可以看出:

Class.getResourceAsStream() 代理给了加载该 class 的 ClassLoader 去实现,调用 classLoader.getResourceAsStream(),如果该类的 ClassLoader 为 null,说明该 class 一个系统 class,所以委托给 ClassLoader.getSystemResourceAsStream。

这一点也印证了之前讲解的原理:资源文件都是由ClassLoader负责加载的,类也是一种resources文件

但通过Object.class.getResourceAsStream()不一定可以搜索到指定的资源文件,原因就在于前面说过的类加载器的搜索范围,所以这种方式并不推荐使用。

5.结语

关于如何正确读取ClassPath下的资源文件相信你已经掌握了正确姿势。

我是薛勤,咱们下期见!关注我,带你领略更多编程技能!

读取ClassPath下resource文件的正确姿势的更多相关文章

  1. Java读取classpath下的文件

    写Java程序时会经常从classpath下读取文件,是时候该整理一下了,并在不断深入的过程中,陆续补充上. 现在Java project 都以maven项目居多, 比如像下面这样的一个项目结构: 编 ...

  2. 【转】SpringBoot——web项目下读取classpath下的文件心得

    在读取springBoot+gradle构建的项目时,如果使用传统的FileInputStream读取文件流或者ResourceUtils工具类的方式,都会失败,下面解释原因: 一.读取文件的三种方式 ...

  3. java读取classpath下properties文件注意事项

    1.properties文件在classpath根路径下读取方式 Properties properties = new Properties(); properties.load(BlogIndex ...

  4. @Value加载classpath下的文件

    maven工程中,要加载classpath下的文件并以InputStream的形式返回,通常使用的方法是 InputStream inputStream = Test.class.getClassLo ...

  5. windows系统下npm升级的正确姿势以及原理

    本文来自网易云社区 作者:陈观喜 网上关于npm升级很多方法多种多样,但是在windows系统下不是每种方法都会正确升级.其中在windows系统下主要的升级方法有以下三种: 首先最暴力的方法删掉no ...

  6. php读取目录下的文件

    工作需要写了一个读取指定目录下的文件,并显示列表,点击之后读取文件中的内容 高手拍砖,目录可以自由指定,我这里直接写的是获取当前文件目录下面的所有文件 <?php /** * 读取指定目录下面的 ...

  7. android 读取根目录下的文件或文件夹

    @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setC ...

  8. spring boot读取classpath下的json文件

    import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resour ...

  9. PHP 简易读取文件目录下的文件,生成css spirte图片

    因为个人不是对PS熟悉,不清楚如何在PS中生成一张横向有序的spirte图片,使用了"css sprite V4.3"版本,生成的图片会出现压缩图片大小的情况,本想修改原作者开发的 ...

随机推荐

  1. hdu 2046 骨牌铺方格

    #include<stdio.h> int main(void) { long long i,n,narr[55]; narr[1]=1;narr[2]=2;narr[3]=3; for( ...

  2. url、href、src 详解

    发现自己居然没把url.href.src关系及使用搞清楚,今天就理一下.主要包括:url.src.href定义以及使用区别.顺便试下在segmentfault来一发. URL(Uniform Reso ...

  3. Matlab删除NaN数据

    删除包含NaN的行: a(any(isnan(a), 2),:) = []; 删除全部为NaN的行: a(all(isnan(a), 2),:) = [];

  4. Ubuntu 12.04 和 Win7 双系统安装

    Thinkpad T400上成功安装双系统 安装Win7 使用光盘按步骤安装,到这里是一个没有分区的硬盘,做了如下分区: 100M(系统保留),40G(C盘),60G(D盘),80G(E盘),52G( ...

  5. powershell 生成随机用户信息

    #生成随机用户信息,包括姓名.QQ.邮箱,手机号 $nameArr=@('一','丁','三','专','世','业','丝','中','丰','临','丹','丽','举','乃','义', '乐' ...

  6. svg-sprite使用

    chainWebpack(config) { config.module .rule('svg') .exclude.add(path.resolve(__dirname,'src/assets/ic ...

  7. WPF 主窗口关闭时结束所有相关线程

    程序主窗口的 Closed 事件中添加代码: Process.GetCurrentProcess().Kill();

  8. spring如何解决单例循环依赖问题?

    更多文章点击--spring源码分析系列 1.spring循环依赖场景2.循环依赖解决方式: 三级缓存 1.spring循环引用场景 循环依赖的产生可能有很多种情况,例如: A的构造方法中依赖了B的实 ...

  9. ICTCLAS中的HMM人名识别

    http://www.hankcs.com/nlp/segment/ictclas-the-hmm-name-recognition.html 本文主要从代码的角度分析标注过程中的细节,理论谁都能说, ...

  10. C#MVC和cropper.js实现剪裁图片ajax上传的弹出层

     首先使用cropper.js插件,能够将剪裁后的图片返回为base64编码,后台根据base64编码解析保存图片. jQuery.cropper: 是一款使用简单且功能强大的图片剪裁jquery插件 ...