【踩坑记录】关于jar包找不到resources目录下文件的异常
问题描述
在项目中,使用到了resources
文件下的数据文件,使用的是File
方式:
// ResourceUtils.CLASSPATH_URL_PREFIX 值为:"classpath:"
// cityProperties.getPath(); 为要使用的文件地址
String path = ResourceUtils.CLASSPATH_URL_PREFIX + cityProperties.getPath();
File citys = ResourceUtils.getFile(path);
if (!citys.exists()) {
return;
}
...
本地运行没有问题,打成jar包后运行会有FileNotFoundException
报错(未找到文件):
java.io.FileNotFoundException: class path resource [xxx] cannot be resolved to absolute file path because it does not reside in the file system:
jar:file:/starcharge/starcharge.jar!/BOOT-INF/classes!/[xxx]
解决方案
【推荐方案】将File
操作资源的方式替换为使用流的方式读取资源
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 这里了的例子是使用了 resources/city 文件夹下多个文件类型为 xxx.csv 的文件
Resource[] resources = resolver.getResources("city/**.csv");
if (resources.length < 1 ) {
throw new IOException("读取文件出错");
}
System.out.println(resources.length);
for (Resource resource : resources) {
try (InputStream input = resource.getInputStream()) {
List allLines = IOUtils.readLines(input);
...
}
...
}
注意:如果你在getResources( ) 中写入的只有文件夹名称的话,会报数组越界的异常。
如果你使用的是单个文件则可以更改为:
// 注意这里的文件名字前不能加"/",否则也会出现上述异常
Resource resource = resolver.getResource("[文件名字].[文件格式]");
【其余解决方案思路】并没有全部都试过,在理论上可以解决
- 使用绝对路径(jar包的资源文件路径会发生变化,后续分析的时候会说)
- 使用ClassPathResource获得文件资源并获取资源文件流
- 通过利用ClassLoader读取Jar包内部文件,ClassLoader 是类加载器的抽象类。它可以在运行时动态的获取加载类的运行信息。我们真正写代码的时候,是通过Class类中的
getResource()
和getResourceAsStream()
方法,这两个方法会委托ClassLoader中的getResource()
和getResourceAsStream()
方法 。public void getResource() throws IOException{ //返回读取指定资源的输入流 InputStream is=this.getClass().getResourceAsStream("/resource/res.txt"); BufferedReader br=new BufferedReader(new InputStreamReader(is)); String s=""; while((s=br.readLine())!=null) System.out.println(s); }
- 还有一种是通过DefaultResourceLoader获得文件资源保存一个缓存文件在项目中,然后去操作缓存文件(这种方式可能会出现:
java.lang.IllegalArgumentException: URI is not hierarchical
异常信息,在这里不做讨论)
【方法2】【方法3】本质还是通过流去操作文件
原因分析
jar包中的类源代码用File f=new File(相对路径);
的形式,是不可能定位到文件资源的。因为在本地运行时,src/main/
下面的java和resources文件夹都被(编译)打包到了target生产包的classes/
目录下,这个时候,classes这个文件夹,它就是我们要找的classpath。所以在本地运行是没有问题的。
而在打成jar包以后,这里的结构就发生了变化。我们可以通过命令[jar包名称].jar $ tree
来查看jar包的结构。可以发现此时classes这个文件夹被放在了BOOT-INF文件夹下,所以通过File
使用的相对路径并不是文件资源定位符的格式(jar中的资源有其专门的URL形式:jar:
,因此可以定位到文件资源。),因此就不可能获得到真正的位置,自然也就会报文件没有找到的异常信息了。