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

Posted 石头StoneWang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了打包部署后无法读取jar包里的文件(实测可行,Java中读取jar包中的文件)相关的知识,希望对你有一定的参考价值。

打包部署后无法读取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包内的路径
public static InputStream readInputStreamFromJar(String jarPath, String fileInJar) {
    JarFile jarFile = null;
    InputStream input = null;
    try {
        jarFile = new JarFile(jarPath);
        JarEntry jarEntry = jarFile.getJarEntry(fileInJar);
        input = jarFile.getInputStream(jarEntry);
        return input;
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        // 千万别关闭 jarFile,即使在这里没有关闭 inputStream,但是关闭 jarFile相当于关闭了 inputStream,
        // 会导致 inputStream 虽然不是null但是available=0即没有内容了!!!
        // IOUtils.closeQuietly(jarFile);
    }
}

上述接口如何传参?

按下面的方法获得需要的入参

  • JarPathResult 不列出代码了,就是个装返回结果的类
private static JarPathResult getJarResult(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 = new JarPathResult();
    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")
public Map<String, String> getPath() {
    Map<String, String> map = new HashMap<>();
    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

以上是关于打包部署后无法读取jar包里的文件(实测可行,Java中读取jar包中的文件)的主要内容,如果未能解决你的问题,请参考以下文章

java工程打包部署到linux下根据url找jar包里的文件找不到,请教。

jar方式运行项目-读取jar包中的文件

jar命令更新SpringBoot项目jar包里的补丁文件

jar命令更新SpringBoot项目jar包里的补丁文件

jar命令更新SpringBoot项目jar包里的补丁文件

jar命令更新SpringBoot项目jar包里的补丁文件