打包部署后无法读取jar包里的文件
Java中读取jar包中的文件
linux中无法读取jar包中的内容(windows可以的!),如何解决
一、背景
项目中免不了需要读取文件,如果文件用绝对路径读取,就需要配置或写死路径,非常不便。如果我们读取类路径上的文件,就不会这么麻烦。
比如要读取的文件位于类路径下 java/main/resources/myfile.txt,这个文件在项目打成jar包的时候,也会压缩在里头,非常方便。
用this.getClass().getResource(resourcePath)
或this.getClass().getResourceAsStream(resourcePath)
获得文件路径或输入流。
上面2个方法就是从类路径上读取文件的,但有个大坑,就是你在IDEA里调试得好好的,但打成jar包,启动项目后发现可能会无法读取到这个文件。详细如下
打成jar包在linux启动,但是读取输入流为null
打成jar包在windows启动,能读取到输入流 (很奇葩,linux不能windows居然能)
在 IDEA 里:能读取到这个输入流
怎么解决
我实际验证了,下面的方式可行。
二、如何解决
改成读取jar包的方式读取jar包内的文件
如下这个类,就可以读取到输入流。。
- JarFile 等是JDK自带的
- 两个参数,第一个是jar包的位置,第二个是要读取的文件在jar包内的路径
publicstaticInputStreamreadInputStreamFromJar(String jarPath,String fileInJar){JarFile jarFile=null;InputStream input=null;try{
jarFile=newJarFile(jarPath);JarEntry jarEntry= jarFile.getJarEntry(fileInJar);
input= jarFile.getInputStream(jarEntry);return input;}catch(IOException e){thrownewRuntimeException(e);}finally{// 千万别关闭 jarFile,即使在这里没有关闭 inputStream,但是关闭 jarFile相当于关闭了 inputStream,// 会导致 inputStream 虽然不是null但是available=0即没有内容了!!!// IOUtils.closeQuietly(jarFile);}}
上述接口如何传参?
按下面的方法获得需要的入参
- JarPathResult 不列出代码了,就是个装返回结果的类
privatestaticJarPathResultgetJarResult(String path){int firstMark= path.indexOf("!");int length="jar:file:".length();String jarFile= path.substring(length, firstMark);// 截出来win是/D:/... ,但后续仍然正常运行String fileInJar= path.substring(firstMark+"!/".length()).replace("!","");// 去掉开头的!/开头JarPathResult result=newJarPathResult();
result.setJarFile(jarFile);
result.setFileInJar(fileInJar);return result;}
getJarResult(path)的入参说明
入参说明:
传入类似下面的格式的路径
jar:file:/root/app/read-file-from-jar-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/sub/subfile.txt
(即传入 `this.getClass().getResource("/sub/subfile.txt").toString()` 的值)
出参说明:
/root/app/read-file-from-jar-0.0.1-SNAPSHOT.jar
BOOT-INF/classes/sub/subfile.txt
补充:
对于
this.getClass().getResource("/sub/subfile.txt").toString()
**怎么知道要写成 /sub/subfile.txt ? **
你的文件放在 java/main/resources 下的,以这个为根路径,以/开头,定位到你的文件。
- 比如 java/main/resources/abc.txt 就写成 /abc.txt
- 又如 java/main/resources/aa/bb/abc.txt 就写成 /aa/bb/abc.txt。
注意:
- 如果你乱写一个文件不存在或者路径写错,就会导致 getResource 返回null,toString之前是最好判断非null的
- 在 IDEA 里跑会报错,因为IDEA 里获取的路径不是 jar:file:/ 开头的!!!可以判断一些,如果是 jar:file:/ 开头的用JAR包的方式读取,如果是 file:/ 开头,直接用 this.getClass().getResourceAsStream() 即可。
附录:关于 getResource 读取的路径
this.getClass().getResource(resourcePath)
返回URL,这个URL#toString() 后就得到一个路径
有以下文件,分别放在:
src/main/resources/myfile.txt
src/main/resources/sub/myfile.txt
设计了如下的代码,在IDEA里启动项目后请求接口。之后再打jar包,并分别在windows、linux中启动
@GetMapping("/getPath")publicMap<String,String>getPath(){Map<String,String> map=newHashMap<>();String resourcePath1="/myfile.txt";String resourcePath2="/sub/subfile.txt";URL filepathUrl1=this.getClass().getResource(resourcePath1);URL filepathUrl2=this.getClass().getResource(resourcePath2);String filepath1= filepathUrl1==null?null: filepathUrl1.toString();String filepath2= filepathUrl2==null?null: filepathUrl2.toString();
map.put(resourcePath1+" 的绝对路径是:", filepath1);
map.put(resourcePath2+" 的绝对路径是:", filepath2);return map;}
读取的结果是:
可以自行观察路径的规律。
在IDEA 里运行:
file:/D:/DevFolder/code/hello/read-file-from-jar/target/classes/myfile.txt
file:/D:/DevFolder/code/hello/read-file-from-jar/target/classes/sub/subfile.txt
打包后再win里运行:
jar:file:/D:/DevFolder/code/hello/read-file-from-jar/target/read-file-from-jar-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/myfile.txt
jar:file:/D:/DevFolder/code/hello/read-file-from-jar/target/read-file-from-jar-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/sub/subfile.txt
打包后再linux里运行:
jar:file:/root/app/read-file-from-jar-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/myfile.txt
jar:file:/root/app/read-file-from-jar-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/sub/subfile.txt
总结:
- 直接在 IDEA 中运行,会得到
file:/...
- 打成jar包后运行,得到
jar:file:/...
jar:file:/...
的格式有两个!
号,信息被分成了3段- 第一段是jar包的位置,比如 /root/app/read-file-from-jar-0.0.1-SNAPSHOT.jar
- 第二段是classpath的路径,比如 /BOOT-INF/classes
- 第三段是文件真正在什么路径,比如 /myfile.txt 或者 /sub/subfile.txt