【踩坑记录】关于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("[文件名字].[文件格式]");

【其余解决方案思路】并没有全部都试过,在理论上可以解决

  1. 使用绝对路径(jar包的资源文件路径会发生变化,后续分析的时候会说)
  2. 使用ClassPathResource获得文件资源并获取资源文件流
  3. 通过利用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);  
     }  
    
  4. 还有一种是通过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:!/{entry} ,因此可以定位到文件资源。),因此就不可能获得到真正的位置,自然也就会报文件没有找到的异常信息了。