读取ClassPath下resource文件的正确姿势
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文件的正确姿势的更多相关文章
- Java读取classpath下的文件
写Java程序时会经常从classpath下读取文件,是时候该整理一下了,并在不断深入的过程中,陆续补充上. 现在Java project 都以maven项目居多, 比如像下面这样的一个项目结构: 编 ...
- 【转】SpringBoot——web项目下读取classpath下的文件心得
在读取springBoot+gradle构建的项目时,如果使用传统的FileInputStream读取文件流或者ResourceUtils工具类的方式,都会失败,下面解释原因: 一.读取文件的三种方式 ...
- java读取classpath下properties文件注意事项
1.properties文件在classpath根路径下读取方式 Properties properties = new Properties(); properties.load(BlogIndex ...
- @Value加载classpath下的文件
maven工程中,要加载classpath下的文件并以InputStream的形式返回,通常使用的方法是 InputStream inputStream = Test.class.getClassLo ...
- windows系统下npm升级的正确姿势以及原理
本文来自网易云社区 作者:陈观喜 网上关于npm升级很多方法多种多样,但是在windows系统下不是每种方法都会正确升级.其中在windows系统下主要的升级方法有以下三种: 首先最暴力的方法删掉no ...
- php读取目录下的文件
工作需要写了一个读取指定目录下的文件,并显示列表,点击之后读取文件中的内容 高手拍砖,目录可以自由指定,我这里直接写的是获取当前文件目录下面的所有文件 <?php /** * 读取指定目录下面的 ...
- android 读取根目录下的文件或文件夹
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setC ...
- spring boot读取classpath下的json文件
import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resour ...
- PHP 简易读取文件目录下的文件,生成css spirte图片
因为个人不是对PS熟悉,不清楚如何在PS中生成一张横向有序的spirte图片,使用了"css sprite V4.3"版本,生成的图片会出现压缩图片大小的情况,本想修改原作者开发的 ...
随机推荐
- JS顶级对象window
<script type="text/javascript"> var num = 100; alert(num); wind ...
- SICP 1.9-1.10
1.9 2^102^162^16 2n2^(n)2的(n-1)层次方(每一层都是2次方) 比如 h(4) = 2^(2^(2^2)) = 2^16
- 一个字体,大小,颜色可定义的自绘静态框控件-XColorStatic 类(比较好看,一共19篇自绘文章)
翻译来源:https://www.codeproject.com/Articles/5242/XColorStatic-a-colorizing-static-control XColor Stati ...
- Win8 Metro(C#)数字图像处理--2.72图像噪声
原文:Win8 Metro(C#)数字图像处理--2.72图像噪声 [函数名称] 噪声函数WriteableBitmap NoiseProcess(WriteableBitmap src, ...
- C#如何将十六进制数字符串转byte[]?
代码: /// <summary> /// 16进制原码字符串转字节数组 /// </summary> /// <param name="hexString&q ...
- DBLINK学习
1.连接本地scott用户查看拥有的表 [oracle@ORADG ~]$ sqlplus scott/tiger SQL> select * from tab; TNAME ...
- Delphi获得一个进程的主窗体(GetWindow(AHandle, GW_OWNER)等于0的窗体才是主窗体,并且要IsWindowVisible排除Application窗口)
type TMainWindow = packed record ProcessID: THandle; MainWindow: THandle; end; PMainWindow ...
- DUI-模态对话框的实现
模态对话框要求自己实现自己的消息循环,当然,建议它还是处于主线程中,所以最好是由它再调用主线程的消息循环函数,此时主线程自身的消息循环函数被阻塞,等待模板对话框的消息循环函数退出 参考代码如下: 1 ...
- Windows10中的IIS10安装php manager
Windows10中自带的IIS:Microsoft-IIS/10.0. 然后这个10却让原本支持组件无法安装了,php manager组件安装时提示“必须安装IIS7以上才可以安装”. 那是不是真的 ...
- tkinter + cefpython 仿美团桌面程序
使用js开发桌面程序目前是一个趋势,Electron是其中一个佼佼者,网上也不乏很多文章.今天主要是来讲一下cefpython. 用python的朋友,特别使用过tkinter开发过界面的,一定会觉得 ...