问题形容
在 Java 我的项目中,若须要拜访资源文件,通常应用 getClass().getResource("/xxx")
和getClass().getResourceAsStream("/")
来获取资源文件,后一种形式获取到的是 InputStream,个别不会呈现什么问题,然而通过第一种获取资源 URL 的形式,在 IDEA 中执行时没有问题,然而在服务器环境执行时,会呈现 java.nio.file.FileSystemNotFoundException
,之前遇到这个问题我没有在意,只是换成getResourceAsStream
后就解决了,然而起初遇到一个须要递归拜访资源文件目录,我应用的 Files.walkFileTree
办法,间接获取输出流的计划不适宜我,随后发现其实只有是打成 jar 包执行的时候,通过 Files
和Path
去拜访资源文件,getResource
就会抛出 FileSystemNotFoundException
,并非服务器环境的问题,因为 jar 包执行时,文件系统与 IDE 中间接执行时不一样,所以会抛出这个异样。
原始的代码如下:
Path configDir = Paths.get(
LocalConfigDataFlowManager.class.getResource("/").toURI().getPath(), "flow-config"
);
List<Map<String, Object>> config = new ArrayList<>();
Files.walkFileTree(
configDir,
new HashSet<>(),
1,
new FileVisitor<Path>() {......
报错信息如下:
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 ink.andromeda.dataflow.core.flow.ConfigurableDataFlowManager.<init>(ConfigurableDataFlowManager.java:305)
at ink.andromeda.dataflow.demo.LocalConfigDataFlowManager.<init>(LocalConfigDataFlowManager.java:25)
at ink.andromeda.dataflow.demo.DataFlowDemoApplication.dataFlowManager(DataFlowDemoApplication.java:51)
at ink.andromeda.dataflow.demo.DataFlowDemoApplication$$EnhancerBySpringCGLIB$$73b01d37.CGLIB$dataFlowManager$0(<generated>)
......
......
2021-01-07.22:34:39.649 ERROR TraceId[] [main] i.a.d.d.LocalConfigDataFlowManager.visitFileFailed:60 null/flow-config
java.nio.file.NoSuchFileException: null/flow-config
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
at java.base/sun.nio.fs.UnixFileAttributeViews$Basic.readAttributes(UnixFileAttributeViews.java:55)
at java.base/sun.nio.fs.UnixFileSystemProvider.readAttributes(UnixFileSystemProvider.java:149)
at java.base/java.nio.file.Files.readAttributes(Files.java:1763)
at java.base/java.nio.file.FileTreeWalker.getAttributes(FileTreeWalker.java:219)
at java.base/java.nio.file.FileTreeWalker.visit(FileTreeWalker.java:276)
at java.base/java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:322)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2716)
at ink.andromeda.dataflow.demo.LocalConfigDataFlowManager.getFlowConfig(LocalConfigDataFlowManager.java:39)
at ink.andromeda.dataflow.core.flow.ConfigurableDataFlowManager.reload(ConfigurableDataFlowManager.java:92)
at ink.andromeda.dataflow.core.flow.ConfigurableDataFlowManager.init(ConfigurableDataFlowManager.java:56)
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)
......
解决方案
刚开始试了如下计划:
Path configDir = FileSystems.getDefault().getPath(new UrlResource(this.getClass().getResource("/flow-config").toURI()).toString());
然而依然有问题:
2021-01-07.23:06:42.037 ERROR TraceId[] [main] i.a.d.d.LocalConfigDataFlowManager.visitFileFailed:63 URL [jar:file:/Users/windlively/IdeaProjects/data-flow/data-flow-demo/target/data-flow-demo-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/flow-config]
java.nio.file.NoSuchFileException: URL [jar:file:/Users/windlively/IdeaProjects/data-flow/data-flow-demo/target/data-flow-demo-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/flow-config]
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
at java.base/sun.nio.fs.UnixFileAttributeViews$Basic.readAttributes(UnixFileAttributeViews.java:55)
at java.base/sun.nio.fs.UnixFileSystemProvider.readAttributes(UnixFileSystemProvider.java:149)
at java.base/java.nio.file.Files.readAttributes(Files.java:1763)
at java.base/java.nio.file.FileTreeWalker.getAttributes(FileTreeWalker.java:219)
at java.base/java.nio.file.FileTreeWalker.visit(FileTreeWalker.java:276)
at java.base/java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:322)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2716)
at ink.andromeda.dataflow.demo.LocalConfigDataFlowManager.getFlowConfig(LocalConfigDataFlowManager.java:42)
at ink.andromeda.dataflow.core.flow.ConfigurableDataFlowManager.reload(ConfigurableDataFlowManager.java:92)
at ink.andromeda.dataflow.core.flow.ConfigurableDataFlowManager.init(ConfigurableDataFlowManager.java:56)
于是持续查问材料,在 stackoverflow 上看到了一个利用 Spring 的 @Value()
注解和 Spring Resource 来拜访资源文件的代替计划,而且提供了遍历资源文件夹的办法。
首先在 Bean 中退出一个字段并用 Value 注解注入资源文件的地位:
@Value("classpath:/flow-config/**")
private Resource[] flowConfigResources;
Spring 会主动将 classes 根目录 flow-config
文件夹下的资源文件全副注入到 flowConfigResources
字段中去。
而后批改原先的代码如下:
protected List<Map<String, Object>> getFlowConfig() {return Stream.of(flowConfigResources)
.filter(s -> Objects.nonNull(s.getFilename()))
.filter(s -> s.getFilename().matches("^sync-config-[\\w-]+?.json$"))
.map(flowConfigResource -> {try (InputStream is = flowConfigResource.getInputStream()) {ByteArrayOutputStream os = new ByteArrayOutputStream();
int data;
while ((data = is.read()) != -1) os.write(data);
//noinspection unchecked
return (Map<String, Object>) GSON().fromJson(os.toString(StandardCharsets.UTF_8.name()),
new TypeToken<Map<String, Object>>() {}.getType());
} catch (IOException ex) {throw new IllegalStateException(ex);
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
过滤和遍历 Spring 注入的 Resource 列表,即可读取到此文件夹下所有须要的资源文件,打成 jar 包之后也可失常运行。