读取jar文件内部的配置信息是在进行开发基于java程序组件时必然会遇到的问题,这里所遇到的问题是在开发测试和部署(也就是将程序打成jar包之后供其他组件调用)时往往会不一致。也就是开发的时候我们的代码可以访问到配置文件信息,但是一旦打成jar包之后往往会遇到“FileNotFoundException”,也就是无法找到配置文件。下面看一个示例。

 

项目结构图 

这是一个测试项目,项目的结构如图所示,现在需要在Main.java类中读取conf.properties文件中的信息。



不一致行为

如果我们在代码中使用下面的语句

 

  1. ...  
  2. String confPath = "src/conf/conf.properties";  
  3. FileInputStream fis = new FileInputStream(new File(confPath));  
  4. config.load(fis);  
  5. ... 

那么我们打成jar包之前运行程序是可以读取到所要信息,但是打成jar包之后必然会遇到“FileNotFoundException”。而如果使用下面的代码

 

  1. ...    
  2. String confPath = "conf/conf.properties";  
  3. FileInputStream fis = new FileInputStream(new File(confPath));  
  4. config.load(fis);    
  5. ...  

那么无论是打包之前还是之后都会遇到“FileNotFoundException”的异常!

正确的做法

  1. ...  
  2. String confPath = "conf/conf.properties";  
  3. config.load(this.getClass().getClassLoader().getResourceAsStream(confPath));  
  4. ... 

这时,无论是在打jar包之前还是之后都可以正确读取conf.properties文件中的信息!

原因分析

上面两种截然不同的结果产生的原因是我们使用的FileInputStream和getResourceAsStream()方法的差异所造成的!所以原因也必然在于FileInputStream和getResourceAsStream()的不同。

根据JDK中的表述,FileInputStream是基于当前JVM所在的OS,在文件系统指定的路径中进行查找要求的资源。而ClassLoader类的getResourceAsStream()方面的描述如下:

 
public InputStream getResourceAsStream(String name)
返回读取指定资源的输入流。

getResource(String) 的文档中描述了搜索顺序。

参数:
name - 资源名称
返回:
用于读取资源的输入流,如果无法找到资源,则返回 null
从以下版本开始:
1.1
public URL getResource(String name)
查找具有给定名称的资源。资源是可以通过类代码以与代码基无关的方式访问的一些数据(图像、声音、文本等)。

资源名称是以 '/' 分隔的标识资源的路径名称。

此方法首先搜索资源的父类加载器;如果父类加载器为 null,则搜索的路径就是虚拟机的内置类加载器的路径。如果搜索失败,则此方法将调用 findResource(String) 来查找资源。

 

参数:
name - 资源名称
返回:
读取资源的 URL 对象;如果找不到该资源,或者调用者没有足够的权限获取该资源,则返回 null
从以下版本开始:
1.1


 
从这段文字表述中我们不难发现,getResourceAsStream()在查找资源时跟JVM所用的OS毫无关系,甚至跟资源所在文件系统的路径也是无关的,它是基于类路径进行查找的!也就是说,当jar包或*.class文件加载之后,JVM会根据jar包或*.class文件所在的classpath属性去查找指定的资源,而这个classpath是在jar包的MANIFEST.MF文件中指定的,如下所示:

Manifest-Version: 1.0

Class-Path: .

Main-Class: com.webex.app.Main

这里的“.”代表当前路径,是一个相对路径,但是它相对的是一个“虚拟路径”,也就是jar或*.class文件加载到JVM后的“路径”!