java.io.FileNotFoundException: file:/xxx/xxx.jar!/BOOT-INF/classes!/xxx.xlsx (没有那个文件或目录)

解决EasyExcel在导出动态表头和合并单元格时的问题,介绍了一种通过两次导出实现的方法,并针对Linux环境下文件路径读取错误给出了具体解决方案。

🎈 1 参考文档

填充Excel | Easy Excel官方文档

【RuoYi-Vue-Plus】问题笔记 04 - EasyExcel 导出 Excel 问题合集 | pudn-Michelle_Zhong

基于EasyExcel模板填充方式进行二次导出(动态表头、合并单元格问题处理) | 优快云-Cau1i


🔍2 问题描述

2.1 需要导出的Excel文件

在这里插入图片描述

2.2 刚开始的解决方案

一般都是固定表头,然后填充数据,相当于一维的。因为表头是动态的,所以第二部分数据相当于二维的,需要将表头表格数据分别进行填充。

在这里插入图片描述

EasyExcel的填充方式是通过模板进行填充导出的,那我们可以导出两次,第一次用/resources/template下的模板文件将Excel导出到本地,此时还能立马查看、验证第一次导出的结果是否正确,接着以第一次导出的Excel文件,作为第二次导出的模板,最后再导出需要的Excel表格文件,最后有必要的话再删除第一次导出的Excel文件。

模板:

在这里插入图片描述

第一次导出:

在这里插入图片描述

第二次导出:

在这里插入图片描述

2.3 简化后的代码

// 获得文件当前路径
String getFilePath = this.getClass().getResource("/").getPath();
// 获取模板文件
String screenTemplateFileName = getFilePath + "template" + File.separator + "screenTemplate.xlsx";
// 导出的临时模板文件名称
String screenTemporaryTemplateFileName = getFilePath + "template" + File.separator + "screenTemporaryTemplate.xlsx";
// 这里使用try-with-resource
try (
	OutputStream screenOut = new FileOutputStream(screenTemporaryTemplateFileName);
	BufferedOutputStream screenBos = new BufferedOutputStream(screenOut);

	OutputStream finalScreenOut = httpServletResponse.getOutputStream();
	BufferedOutputStream finalScreenBos = new BufferedOutputStream(finalScreenOut);
) {
// --------------------------------基本配置--------------------------------
	......

// ---------------------模拟获取第一部分的表格数据、表头参数---------------------
	......

// --------------------------------第一次导出--------------------------------
ExcelWriter screenTemplateExcelWriter = EasyExcel
        .write(screenBos)   // 导出临时文件
        .withTemplate(screenTemplateFileName)  // 使用的模板
        .build();
screenTemplateExcelWriter.finish();

// ----------------------------模拟获取第二部分数据----------------------------
	......

// --------------------------------第二次导出--------------------------------
ExcelWriter screenScoreExcelWriter = EasyExcel
        .write(finalScreenBos)   // 导出最终文件
        .withTemplate(screenTemporaryTemplateFileName)  // 以第一次导出的文件,作为第二次导出的模板
        .build();
screenScoreExcelWriter.finish();
} catch (IOException e) {
	throw new RuntimeException(e);
} 

2.4 本地运行测试导出结果

可以看到临时文件正常导出,最终下载的文件也正常导出。

在这里插入图片描述

在这里插入图片描述

2.5 在Linux上运行测试导出结果

  1. 打开Excel导出文件报错:Excel 无法打开文件“exportFile.xlsx”,因为文件格式或文件扩展名无效。请确定文件未损坏,并且文件扩展名与文件的格式匹配。在这里插入图片描述

  2. 查看日志。

    java.io.FileNotFoundException: file:/home/hyc/java/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/template/screenTemporaryTemplate.xlsx (没有那个文件或目录)
            at java.io.FileOutputStream.open0(Native Method) ~[na:1.8.0_345]
            at java.io.FileOutputStream.open(FileOutputStream.java:270) ~[na:1.8.0_345]
            at java.io.FileOutputStream.<init>(FileOutputStream.java:213) ~[na:1.8.0_345]
            at java.io.FileOutputStream.<init>(FileOutputStream.java:101) ~[na:1.8.0_345]
    		......
    

💡3 原因分析

在Windows环境下中运行没问题,而在Linux环境下就不行。

把代码打成jar包之后,放在Linux环境上运行,在Linux中无法直接访问未经解压的文件,所以无法通过路径去读取文件的。


🚀4 解决方案

最简单的解决方案:让第一次直接导出Excel流,而不导出Excel文件,就不需要去resources下获取临时模板文件。

EasyExcel也支持读取模板文件的时候使用流:

public ExcelWriterBuilder withTemplate(InputStream templateInputStream) {
    this.writeWorkbook.setTemplateInputStream(templateInputStream);
    return this;
}

完整解决方案参考另一篇博客:基于EasyExcel模板填充方式进行二次导出(动态表头、合并单元格问题处理)

Spring Boot应用中,`java.io.FileNotFoundException` 通常出现在尝试访问JAR包内部文件时,特别是在使用 `File` `FileInputStream` 直接操作资源路径时。这种问题的根本原因在于JavaJAR文件系统(`jar:file:/...`)与本地文件系统(`file:/...`)的行为差异,导致某些API(如 `File` 类)无法直接访问JAR内部资源[^1]。 ### 文件访问方式应避免使用 `File` 类 当使用 `File file = new File("file:/work/app.jar!/BOOT-INF/classes!/files/03test.docx")` 这种方式访问资源时,会抛出 `FileNotFoundException`,因为 `File` 类无法解析JAR内部的路径结构。JAR包是一个压缩文件,内部资源不能直接作为 `File` 对象访问。类似问题在Spring框架中也可能出现,例如在使用 `ResourceLoader` 获取资源后调用 `.getFile()` 方法时[^4]。 ```java Resource resource = resourceLoader.getResource("classpath:files/03test.docx"); File file = resource.getFile(); // 这里会抛出异常,当资源在JAR中时 ``` ### 使用 `InputStream` `Resource` 接口访问资源 为了解决这个问题,应避免使用 `File` 类,而应使用 `InputStream` `Resource` 接口来读取资源内容。这种方式适用于文件在类路径下(`classpath:`)JAR包内的场景。 ```java @Autowired private ResourceLoader resourceLoader; public void readFile() throws IOException { Resource resource = resourceLoader.getResource("classpath:files/03test.docx"); try (InputStream is = resource.getInputStream()) { // 处理输入流 } } ``` ### 配置Spring以避免文件系统访问 某些库框架(如FreeMarker)在加载模板时会尝试将资源转换为 `File` 对象,这在资源位于JAR中时会导致异常。为避免此类问题,可以配置Spring不优先使用文件系统访问: ```java @Bean public FreeMarkerConfigurer freeMarkerConfigurer() { FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setPreferFileSystemAccess(false); // 禁用文件系统访问优先 configurer.setTemplateLoaderPath("classpath:/templates/"); return configurer; } ``` ### 构建和资源放置需注意 确保资源文件(如 `03test.docx`)确实存在于 `src/main/resources` 目录下,并在构建后被正确打包到 `BOOT-INF/classes/` 路径中。可以通过解压JAR包验证资源是否存在。 ```bash jar tf app.jar | grep 03test.docx ``` 若资源未被正确包含,应检查 `pom.xml` `build.gradle` 中的资源过滤配置,确保非文本资源(如 `.docx` 文件)不会被排除。 ### 总结 Spring Boot 应用在访问JAR包内资源时,应避免使用 `File` 类 `.getFile()` 方法,而应使用 `InputStream` `Resource` 接口进行处理。同时,在配置模板引擎第三方库时,应设置其不优先访问文件系统。确保资源在构建过程中被正确打包,并在运行时通过类路径访问。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值