写在前面
今天在读取resources下的文件使用时,发现在idea启动运行时可以正常读取,但是部署到服务器上运行时则不可以,报错java.nio.file.FileSystemNotFoundException,这个问题困扰了我挺长时间。所以这里写一个demo来模拟记录一下。
实践
目标是读取resources下的test.xml文件来完成功能。
本地idea读取代码如下
public class ResourceReader {
static {
try {
URI uri = ResourceReader.class.getClassLoader().getResource("test.xml").toURI();
Path source = Paths.get(uri);
System.out.println(source.getFileName());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ResourceReader resourceReader = new ResourceReader();
}
}
可以正常运行并输出文件名test.xml。
但打包代码部署到服务器上后运行时报错如下:
于是很纳闷,在stackflow搜了下这个报错,大致定位了下问题可以知道应该是服务器上使用jar包运行导致与本地环境不一样引起的报错。于是我将项目在本地打成jar包后准备debug做下对比是哪里的问题。jar包debug方式参考。
在windows的idea下执行时,调试进入Paths.get这个方法,可以发现如果uri.getScheme()为file时,FileSystems默认创建了一个DefaultFileSystemHolder,返回了一个默认加载的FileSystemProvider。
而通过jar包执行时,uri.getScheme() 为jar,会走到try to find provider的代码块,这里加载了两个Provider,一个是WindowsFileSystemProvider,一个是ZipFileSystemProvider,继续debug下去会走到ZipFileSystemProvider的getPath方法
进去之后发现该Provider的filesystems为空,所以传入var1获取的值为null
结合stackflow上的回答可知对于给定的uri,需要自己初始化filesystem,
当然,其回答也是参考了zipfilesystemprovider官网给的示例。于是得出如下修改代码,
public class ResourceReader {
static {
try {
URI uri = ResourceReader.class.getClassLoader().getResource("test.xml").toURI();
Map<String, String> env = new HashMap<>();
env.put("create", "true");
FileSystem zipfs = FileSystems.newFileSystem(uri, env);
Path source = Paths.get(uri);
System.out.println(source.getFileName());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ResourceReader resourceReader = new ResourceReader();
}
}
重新打包运行一下,就可以正常运行了。
但是,需要注意的是改动之后的代码只适用于执行jar包时的环境,在idea下直接执行是会报错的,因为idea下选用的provider是WindowsFileSystemProvider,可以发现该provider实现的newFileSystem 会直接报错,需要注释掉。当然你也可以选择判断var是否带jar
来判断是否应该装载FileSystem
。
总结
解决问题时追根溯源,可以提高自己解决问题的能力,也能学到更多的知识,比如上面提到的gradle项目打包,jar包debug等,如果有优秀的源码也能顺便学习一下。