# autopoi word 模板找不到问题

autopoi 基于 word 模板提示文件找不到问题解决

# 异常栈

autopoi 在 springboot 打成 jar 导出 word 提示模板找不到:

D:\deploy\server\soft\soft.jar!\BOOT-INF\classes!\template\template-11.docx (系统找不到指定的路径。)
java.io.FileNotFoundException: D:\deploy\server\soft\soft.jar!\BOOT-INF\classes!\template\template-11.docx (系统找不到指定的路径。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at org.jeecgframework.poi.cache.manager.FileLoade.getFile(FileLoade.java:47)
	at org.jeecgframework.poi.cache.manager.POICacheManager$1.load(POICacheManager.java:48)
	at org.jeecgframework.poi.cache.manager.POICacheManager$1.load(POICacheManager.java:45)
	at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3529)
	at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2278)
	at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2155)
	at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2045)
	at com.google.common.cache.LocalCache.get(LocalCache.java:3951)

# 解决方法

//导出模板代码
document = WordExportUtil.exportWord07("template/template-11.docx", map);
//修改为 通过ClassPathResource加载模板文件
        InputStream fis = null;
        XWPFDocument document = null;
        try {
        //加载模板
            ClassPathResource resource = new ClassPathResource("template/template-11.docx");
            fis = resource.getInputStream();
            document = new MyXWPFDocument(fis);
        } catch (Exception e) {
            log.error("读取模板失败", e);
        } finally {
            try {
                assert fis != null;
                fis.close();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        //根据模板生成word文件
        WordExportUtil.exportWord07(document, map);

# 问题解析

为什么使用 document = WordExportUtil.exportWord07("template/template-11.docx", map); 会报错,提示文件找不到?查看以下源码:

public byte[] getFile(String url) {
		FileInputStream fileis = null;
		ByteArrayOutputStream baos = null;
		try {
			// 先用绝对路径查询,再查询相对路径
			try {
				fileis = new FileInputStream(url);
			} catch (FileNotFoundException e) {
			//这里虽然对springboot 打成jar获取文件进行了处理
				String path = PoiPublicUtil.getWebRootPath(url);
				//但是springboot下,FileInputStream的方式是读取不到jar里边的静态文件,需要调整为 ClassPathResource 读取静态文件
				//FileInputStream方式可以对放在磁盘某个位置的文件进行读取,比如:d盘 D://opt 但是对jar里边的文件是读取不到的
				fileis = new FileInputStream(path);
			}
			baos = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len;
			while ((len = fileis.read(buffer)) > -1) {
				baos.write(buffer, 0, len);
			}
			baos.flush();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			LOGGER.error(e.getMessage(), e);
		} catch (IOException e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			try {
				if (fileis != null)
					fileis.close();
				if (fileis != null)
					baos.close();
			} catch (IOException e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
		LOGGER.error(fileis + "这个路径文件没有找到,请查询");
		return null;
	}
	
	总结: FileInputStream 只能读取文件系统中的文件,而不是 JAR 包中的资源。

# 拓展

springboot 下如果加载文件,可以通过下面几种方式:

# 1. ResourceLoader

简介:ResourceLoader 是 Spring 的通用资源加载器接口,允许你根据不同的路径前缀加载不同类型的资源。它支持从多种位置加载资源,包括类路径、文件系统、URL 等。

灵活性:支持通过不同的前缀加载资源,如:

  • classpath: 表示从类路径加载资源。

  • file: 表示从文件系统中加载资源。

  • http: 或其他 URL 表示通过 URL 访问资源。

  • 用法:当你需要根据资源的位置动态加载资源时,ResourceLoader 更加灵活。通过它,你可以根据路径前缀自动选择资源的加载方式。

@Autowired
private ResourceLoader resourceLoader;

public void loadResource() {
    // 从类路径加载
    Resource resource1 = resourceLoader.getResource("classpath:template/template-11.docx");

    // 从文件系统加载
    Resource resource2 = resourceLoader.getResource("file:/path/to/file.txt");

    // 从 URL 加载
    Resource resource3 = resourceLoader.getResource("http://example.com/file.txt");
}

# 优点

  • 灵活:可以根据前缀自动识别并加载不同类型的资源。
  • 统一接口:你不需要关心资源从哪里加载,它统一提供了 Resource 接口。

# 缺点

  • 如果你只想加载类路径资源,ResourceLoader 可能显得过于通用。

# 2. ClassPathResource

  • 简介ClassPathResource 是专门用于从类路径加载资源的工具,继承自 DefaultResourceLoader。它只适用于加载位于类路径下的资源,通常用于加载项目中的文件(如配置文件、模板文件等),尤其是打包后的 JAR 文件中的资源。
  • 用法:如果你明确知道资源在类路径下,并且不需要支持其他类型的资源路径,可以直接使用 ClassPathResource
public void loadResource() {
    ClassPathResource resource = new ClassPathResource("template/template-11.docx");
    InputStream inputStream = resource.getInputStream();
    // 读取文件内容
}

# 优点

  • 简单明了:专门用于类路径资源加载,适合明确在类路径下的资源。
  • 轻量级:相对比 ResourceLoader 更加轻量,只用于类路径资源。

# 缺点

  • 局限性:只能加载类路径资源,不能处理文件系统或 URL 资源。

# 区别总结

特性 ResourceLoader ClassPathResource
用途 通用的资源加载器,支持多种资源加载方式 专门用于加载类路径资源
支持的资源类型 类路径、文件系统、URL 等 仅限类路径资源
灵活性 高,支持动态路径前缀加载 低,固定为类路径资源加载
适用场景 需要根据不同路径加载不同类型资源时 明确知道资源在类路径下时

# 什么时候用哪一个?

  • ResourceLoader:当你需要灵活地从多种来源(类路径、文件系统、URL)加载资源时,使用 ResourceLoader
  • ClassPathResource:当你只需要从类路径加载资源(如加载项目中的模板文件、配置文件)时,使用 ClassPathResource 会更直接简单。
上次更新: 2024/12/16