打包部署后无法读取jar包里的文件(实测可行,Java中读取jar包中的文件)

2022-08-06 09:49:47

打包部署后无法读取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
  • 作者:石头StoneWang
  • 原文链接:https://blog.csdn.net/w8y56f/article/details/117530635
    更新时间:2022-08-06 09:49:47