项目打包成jar后包无法读取src/main/resources下文件的解决

 更新时间:2022年04月02日 11:28:18   作者:时间静止不是简史  
本文主要介绍了项目打包成jar后包无法读取src/main/resources下文件的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、项目场景

在项目中读取文件时, 使用new File() 出现的一个坑以及解决流程
这种问题不仅在本地文件读取时会遇到, 而且在下载项目下 (例如: src/main/resources目录下) 的文本时, 也会遇到,

二、问题描述

发现问题

原来代码
该代码功能是利用 common.io 包下的FileUtils来读取文件, 放到一个字符串中

String s = FileUtils.readFileToString(new File("src/main/resources/holiday.txt"), "utf-8");

这种路径书写方式 new File("src/main/resources/holiday.txt") , 在本地运行没问题,
但是打包之后在服务器中运行出现了问题. 下面是错误截图

在这里插入图片描述

可以看到在服务器中日志提示: java.io.FileNotFoundException: File 'holiday.txt' does not exist
即: 在打包后, 一开始配置的路径src/main/resources下无法找到该文件

分析问题

项目在打包之后, 位于 resource目录下的文件, 最常见的就是各种Spring配置文件就会打包在 BOOT-INF/classes 目录下
而FIle 在按照原来的文件路径src/main/resources/holiday.txt'去寻找, 必然找不到文件, 因此会报文件找不到的异常

在这里插入图片描述

在定位问题的过程中发现, 这里 提供了一个思路
就是SpringBoot中所有文件都在jar包中,没有一个实际的路径,因此可以使用以下方式

    /**
     * 通过ClassPathResource类获取,建议SpringBoot中使用
     * springboot项目中需要使用此种方法,因为jar包中没有一个实际的路径存放文件
     *
     * @param fileName
     * @throws IOException
     */
    public void function6(String fileName) throws IOException {
        ClassPathResource classPathResource = new ClassPathResource(fileName);
        InputStream inputStream = classPathResource.getInputStream();
        getFileContent(inputStream);
    }

为什么使用 ClassPathResource 后, 可以找到打包后的文件路径?

上面代码的核心就是: 实例化ClassPathResource 对象. 然后调用getInputStream 来获取资源文件

下面我们来分析这些代码
ClassPathResource 在实例化时, 会初始化类加载器 classLoader 并将项目所用到的所有路径加载到类加载器 classLoader 中, 这些路径包括: java运行环境的jar, Maven 项目中的jar, 以及当前项目打包后的jar等(如下图)

在这里插入图片描述

classPathResource.getInputStream 在获取资源文件时, 因为上面我们初始化了一个classLoader.
所以classLoader不为空, 因此会执行 getResourceAsStream 方法, 我们来追一下这个方法

在这里插入图片描述

getResourceAsStream 方法中的getResource是实际的业务处理方法, 我们继续深入

在这里插入图片描述

getResource 方法如下图, 实际的功能就是递归调用自己, 去不断遍历 parent 下的路径, 获取对应的资源文件
那么 parent 又是谁呢? 我们继续往下看

在这里插入图片描述

看到这里我们豁然开朗, 这个神秘的 parent 就是类加载器classLoader!!!
因此getResource 方法就是去不断遍历我们在ClassPathResource实例化时, 创建的类加载器下面的路径!!!(对应第1点)

在这里插入图片描述

三、解决方案

原来读取文件的代码如下

String s = FileUtils.readFileToString(new File("src/main/resources/holiday.txt"), "utf-8");

去查看 File 的构造函数, 看能否通过 InputStream 来构造
从下图看是不行的

在这里插入图片描述

方案一

并且我们发现 org.apache.commons.io没有提供ClassPathResource 作为入参的读取文件的方法.
因此我们必须手写读取文件的方法

在这里插入图片描述

手写的代码如下
主要注意 Resource resource = new ClassPathResource(fileName); is = resource.getInputStream();

    /**
     * Java读取txt文件的内容
     *
     * @param fileName resources目录下文件名称(无需带目录)
     * @return 将每行作为一个单位放到list中
     */
    public static List<String> readTxtFile(String fileName) {
        List<String> listContent = new ArrayList<>();
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        String encoding = "utf-8";
        try {
            Resource resource = new ClassPathResource(fileName);
            is = resource.getInputStream();
            isr = new InputStreamReader(is, encoding);
            br = new BufferedReader(isr);
            String lineTxt = null;
            while ((lineTxt = br.readLine()) != null) {
                listContent.add(lineTxt);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                isr.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return listContent;
    }

方案二

这种方式对代码入侵较小, 核心还是利用 common.io 下的 FileUtils, 具体方法是
利用FileUtils将ClassPathResource.getInputStream 得到的输入流复制到临时文件中, 然后读取这个临时文件
这种方式缺点是: 需要创建临时文件, 如果待读取文件过大, 则重新创建文件和复制操作会消耗一定的空间和时间, 影响性能

  //方式二 利用FileUtils将ClassPathResource.getInputStream 得到的输入流复制到临时文件中
  Resource resource = new ClassPathResource("holiday.txt");
  InputStream inputStream = resource.getInputStream();
  File tempFile = File.createTempFile("temp", ".txt");
  FileUtils.copyInputStreamToFile(inputStream, tempFile);
  
  String s = FileUtils.readFileToString(tempFile, StandardCharsets.UTF_8);

意外出现

到这里又出现了一个问题, 就是我用的测试项目因为在 maven 里面指定了某些格式的文件. 如下配置
因为指定了banner.txt 以及 xml 与 properties结尾的文件作为资源被打包. 所以文件 holiday.txt 运行后还是访问不到
有问题的pom.xml文件如下

	<!-- 资源拷贝插件,实现在打包时自动拷贝java目录下以及resources目录下的xml的配置文件 -->
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/*.xml</include>
					<include>**/*.properties</include>
					<include>**/banner.txt</include>
				</includes>
			</resource>
		</resources>

打包后资源文件截图如下, 从该图中可以看到 holiday.txt 没有被打包进来

在这里插入图片描述

程序运行之后的错误截图

在这里插入图片描述

我们修改下指定打包的配置 <include>**/*.txt</include>
这样配置后, 我们就可以将类路径下的所有txt 文件打包进行项目中了, 打包之后文件位置如下图
或者我们可以去除项目中下面的代码配置, 这样做会默认打包 resources 下面的所有文件

	<!-- 资源拷贝插件,实现在打包时自动拷贝java目录下以及resources目录下的xml的配置文件 -->
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/*.xml</include>
					<include>**/*.properties</include>
					<include>**/*.txt</include>
				</includes>
			</resource>
		</resources>

修改pom文件后, 重新打包后资源文件(从这里可以看到 holiday.txt 被打包进来 )

在这里插入图片描述

总结

在项目内的文件的读取/下载时, 由于本地路径和项目打包后的路径不同. 出现找不到文件的情况,我们只需要例化ClassPathResource(文件名) 对象. 然后调用getInputStream 来获取资源文件.就能获取任意环境下项目内的文件

如果想打算使用其他方式来获取resources 目录下的文件, 可以参见 这篇博客 .核心和上面问题分析差不多, 基本上都是通过类加载器来获取资源文件的输入流进而找到这个文件

到此这篇关于项目打包成jar后包无法读取src/main/resources下文件的解决的文章就介绍到这了,更多相关jar无法读取src/main/resources文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 优化常见的java排序算法

    优化常见的java排序算法

    这篇文章主要介绍了Java编程中快速排序算法的实现及相关算法优化,快速排序算法的最差时间复杂度为(n^2),最优时间复杂度为(n\log n),存在优化的空间,需要的朋友可以参考下
    2021-07-07
  • Java子类实例化总是默认调用父类的无参构造操作

    Java子类实例化总是默认调用父类的无参构造操作

    这篇文章主要介绍了Java子类实例化总是默认调用父类的无参构造操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Java class文件格式之属性详解_动力节点java学院整理

    Java class文件格式之属性详解_动力节点java学院整理

    这篇文章主要介绍了Java class文件格式之属性详解,需要的朋友可以参考下
    2017-06-06
  • 在SpringBoot中使用ResponseBodyAdvice自定义响应的代码实现

    在SpringBoot中使用ResponseBodyAdvice自定义响应的代码实现

    ResponseBodyAdvice是Spring Framework中的一个接口,允许您在将响应写入客户端之前自定义响应,它通常与@ControllerAdvice注释结合使用,以跨多个控制器将全局更改应用于响应主体,本文介绍了如何使用ResponseBodyAdvice的基本概述,需要的朋友可以参考下
    2024-12-12
  • 基于Java SSM框架开发图书借阅系统源代码

    基于Java SSM框架开发图书借阅系统源代码

    本文给大家介绍了基于Java SSM框架开发图书借阅系统,开发环境基于idea2020+mysql数据库,前端框架使用bootstrap4框架,完美了实现图书借阅系统,喜欢的朋友快来体验吧
    2021-05-05
  • Java 合并多个MP4视频文件

    Java 合并多个MP4视频文件

    这篇文章主要介绍了Java 合并多个MP4视频文件的方法,帮助大家利用Java处理视频,提高办公效率,感兴趣的朋友可以了解下
    2020-11-11
  • 详解java中保持compareTo和equals同步

    详解java中保持compareTo和equals同步

    这篇文章主要介绍了详解java中保持compareTo和equals同步的相关资料,需要的朋友可以参考下
    2017-03-03
  • SpringBoot中分页插件PageHelper的使用详解

    SpringBoot中分页插件PageHelper的使用详解

    分页查询是为了高效展示大量数据,通过分页将数据划分为多个部分逐页展示,原生方法需手动计算数据起始行,而使用PageHelper插件则简化这一过程,本文给大家介绍SpringBoot中分页插件PageHelper的使用,感兴趣的朋友一起看看吧
    2024-09-09
  • 详解SpringCloud-OpenFeign组件的使用

    详解SpringCloud-OpenFeign组件的使用

    这篇文章主要介绍了SpringCloud-OpenFeign组件的使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • SpringBoot使用redis生成订单号的实现示例

    SpringBoot使用redis生成订单号的实现示例

    在电商系统中,生成唯一订单号是常见需求,本文介绍如何利用SpringBoot和Redis实现订单号的生成,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-09-09

最新评论