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. asp .net Cookies

    Request.Cookies和Response.Cookies When validating cookies or cookie data from the browser you should ...

  2. VS创建新的本地数据库

    image   image (localdb)\v11.0是VS内置SQL Server+版本号

  3. QML中文件的加载(三种方法)

    在这里小小总结一下QML文件中如何加载QML文件与JavaScript文件. 1.QML文件中加载JavaScript文件 语法: import <ModuleIdentifier> &l ...

  4. String内存结构

    var s: AnsiString; begin s := '1234567890'; showmessage(s); end; 变量s的内存结构为A8 03 01 00 FF FF FF FF 0A ...

  5. mysqldump数据库备份与恢复

    mysqldump -u 用户名 -p 数据库名> 备份的文件名 本文中因服务器为多实例,所以在执行登陆等命令时指定了-S参数,即指定其中一个数据库 备份: mysqldump -u root ...

  6. HUSTOJ的Windows版评判内核(限制内存使用)

    HUSTOJ的Windows版评判内核(一) 作者:游蓝海 个人主页:http://blog.csdn.net/you_lan_hai 2013.4.9 注:最新版本项目地址:https://gith ...

  7. Win7和Vista的安全机制对于应用程序读取配置文件相关操作的影响(虚拟重定向技术)

    今天构造了一个新版本的XXXX软件,并且在纯净的系统下进行了较为全面的测试.测试中也发现了一些问题.其中包括在Win7测试时程序竟然在另一个目录中创建了文件夹和配置文件,并且进行相关读取操作,却并没有 ...

  8. Realm_King 之 .NET 打包详细教程(B)

    上篇(Realm_King 之 .NET 打包详细教程(A))给大家讲述了打包基本的操作,接下来帮助大家如何覆盖安装,希望大家仔细阅读... (一)看到你的解决方案,选中你的安装程序,点击F4会弹出改 ...

  9. jquery 之load post get

    load() 方法从服务器加载数据,并把返回的数据放入被选元素中 load(url,data,fun(responseTxt ,responseTxt,xhr ){}) 必需的 URL 参数规定您希望 ...

  10. 32个Python爬虫项目让你一次吃到撑

    整理了32个Python爬虫项目.整理的原因是,爬虫入门简单快速,也非常适合新入门的小伙伴培养信心.所有链接指向GitHub,祝大家玩的愉快~O(∩_∩)O WechatSogou [1]- 微信公众 ...