问题
读取springboot的resourece中的文件,原代码如下:
private static String readFile(String path) throws IOException {
ClassPathResource resource = new ClassPathResource(path);
Path filePath = Paths.get(resource.getURI());
return Files.readString(filePath);
}
打包后运行,报错:
java.nio.file.FileSystemNotFoundException
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:169)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:155)
at java.base/java.nio.file.Path.of(Path.java:208)
at java.base/java.nio.file.Paths.get(Paths.java:97)
at org.example.hello.service.SeleniumExample.readFile(SeleniumExample.java:37)
at org.example.hello.service.SeleniumExample.translate_phone(SeleniumExample.java:86)
at org.example.hello.controller.CommonController.quora_phone(CommonController.java:49)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
解法
改成
private static String readFile(String path) throws IOException {
try (var inputStream = new ClassPathResource(path, SeleniumExample.class.getClassLoader()).getInputStream()) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
原因
你的项目打成jar包,打开jar包看看发现,文件路径变成了jar包路径\BOOT-INF\classes\文件
。
如果直接用ClassPathResource resource = new ClassPathResource(path);
,打印出来的文件路径是jar:file:/jar包路径!/BOOT-INF/classes!/文件
。
IDEA调试的时候, 资源文件是存在于真实文件系统里的一个文件。
在jar包中, 它不是一个真实文件系统的文件。
为了能用统一的文件系统路径去表示 jar 内的文件, 用了!
!表示这个文件是一个压缩包, 之后的路径则为压缩包内的路径。
正常情况下的 getFile() 操作, 会得到一个 jar 包路径后面加上一个!
号然后再拼接上包内路径的一个路径.
第一个!
在jar包路径!
,这是对的,因为jar包本质就是压缩包。
但是有第二个!
,classes!
,这就是报错的原因,classes不是压缩包,但是ClassPathResource
给classes前面加上了!
,那就找不到。
那么为什么ClassPathResource
会给classes加上一个!
呢?
jar 的入口类其实是 Spring Boot Launcher, 他会为每一个依赖创建一个 ClassLoader, 这样就可以让每个依赖自己读取自己的资源文件而互不冲突.
而用户自己的类是从 /BOOT-INF/classes 开始的, 用户自己的资源文件的根目录也在这里, 所以为了让用户能够正确读到自己的资源文件. 加载用户代码的那个 ClassLoader 的 classpath 从这里开始.
fat-jar 并不是 Java 官方标准, 所以 Java 认为所有 classpath 都是从 jar 的根目录开始的.
于是我们得到的文件路径, 将是 {用户代码根目录}!/{资源文件路径}
大概理解就是:SpringBoot定义了一个classpath。
classpath = classes
文件夹 = 用户代码根目录 = 用户资源文件根目录
所以你获得的目录本来该是 classes\文件
,但是因为{用户代码根目录}!/{资源文件路径}
,变成了classes!\文件
,这就是多了个!
的原因。
参考
https://www.hiczp.com/spring/spring-boot-wu-fa-jia-zai-classpathresource-wen-ti.html
https://blog.youkuaiyun.com/qq_30038111/article/details/116992160
https://blog.youkuaiyun.com/liuxiao723846/article/details/122966413