Java 使用 Freemarker 生成 Word 和 PDF 文件详解 📄📑
在现代软件开发中,文档生成是一个常见且重要的需求。无论是生成报告、合同,还是自动化文档,能够高效、灵活地生成各种格式的文件对于提升工作效率至关重要。Freemarker 作为一款功能强大的模板引擎,结合 Java 的强大生态系统,能够轻松实现 Word 和 PDF 文件的自动化生成。本文将详细介绍如何使用 Java 与 Freemarker 生成 Word 和 PDF 文件,涵盖环境配置、模板设计、代码实现及优化技巧,并辅以示意图和代码示例,帮助您全面掌握这一技术。
一、Freemarker 概述
Freemarker 是一个基于模板的通用工具,用于生成文本输出(如 HTML、XML、Java 源代码等)。它通过定义模板文件和数据模型,将数据动态填充到模板中,生成最终的文档。Freemarker 简洁灵活,易于集成,是 Java 应用中常用的模板引擎之一。
Freemarker 的主要特点
- 灵活性高:支持复杂的模板逻辑,满足多样化的文档生成需求。
- 易于学习:模板语法简单,容易上手。
- 扩展性强:可与多种 Java 库结合使用,实现多种输出格式。
二、项目环境配置
在开始之前,需要配置好 Java 开发环境,并添加所需的依赖库。
1. 环境准备
- Java Development Kit (JDK):建议使用 JDK 8 及以上版本。
- 构建工具:本文以 Maven 为例,管理项目依赖。
2. 添加 Maven 依赖
在项目的 pom.xml
文件中添加以下依赖:
<dependencies>
<!-- Freemarker 模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<!-- Apache POI,用于操作 Word 文件 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- iText,用于生成 PDF 文件 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<!-- Optional: slf4j,用于日志记录 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
解释:
- Freemarker:核心模板引擎,用于生成文本内容。
- Apache POI:用于操作 Word (
.docx
) 文件。 - iText:用于生成 PDF 文件。
- slf4j:日志记录框架,便于调试和监控。
3. 项目结构
推荐的项目结构如下:
project-root/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ └── DocumentGenerator.java
│ │ └── resources/
│ │ ├── templates/
│ │ │ ├── document.ftl
│ │ │ └── document.html.ftl
├── pom.xml
解释:
- DocumentGenerator.java:主类,负责文档生成逻辑。
templates/:存放 Freemarker 模板文件。
document.ftl
:用于生成 Word 文件的模板。document.html.ftl
:用于生成 PDF 文件的 HTML 模板。
三、设计模板文件
模板文件定义了文档的结构和样式,通过占位符填充动态数据。
1. Word 模板 (document.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>姓名:${name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>年龄:${age}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>职位:${position}</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
解释:
- 使用 Freemarker 语法
${variable}
作为占位符,动态填充数据。 - XML 结构符合 Word 的
.docx
格式要求。
2. PDF 模板 (document.html.ftl
)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文档</title>
<style>
body { font-family: Arial, sans-serif; }
.title { font-size: 24px; font-weight: bold; }
.content { font-size: 16px; }
</style>
</head>
<body>
<div class="title">用户信息</div>
<div class="content">
<p>姓名:${name}</p>
<p>年龄:${age}</p>
<p>职位:${position}</p>
</div>
</body>
</html>
解释:
- 使用 HTML 结构和 CSS 样式,定义 PDF 文档的外观。
- 同样使用
${variable}
占位符填充动态数据。
四、实现文档生成逻辑
在 DocumentGenerator.java
中实现生成 Word 和 PDF 文件的逻辑。
1. 导入必要的类
package com.example;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
解释:
- Freemarker:配置和加载模板。
- Apache POI:操作 Word 文档。
- iText:生成 PDF 文档。
- Java IO:文件读写操作。
- Map:存储动态数据模型。
2. 定义数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 30);
data.put("position", "软件工程师");
return data;
}
解释:
- 创建一个
Map
,存储要填充到模板中的数据。
3. 配置 Freemarker
private Configuration getFreemarkerConfiguration() throws IOException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setClassForTemplateLoading(this.getClass(), "/templates");
cfg.setDefaultEncoding("UTF-8");
return cfg;
}
解释:
- Template Loading:设置模板文件的加载路径。
- Encoding:设置模板的默认编码为
UTF-8
,确保中文字符正常显示。
4. 生成 Word 文件
public void generateWord() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.ftl");
Map<String, Object> data = getDataModel();
// 渲染模板到输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(baos);
template.process(data, out);
out.flush();
// 将渲染后的内容写入 Word 文档
InputStream is = new ByteArrayInputStream(baos.toByteArray());
XWPFDocument document = new XWPFDocument(is);
FileOutputStream fos = new FileOutputStream("output.docx");
document.write(fos);
fos.close();
document.close();
System.out.println("Word 文件生成成功!");
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
解释:
- 加载模板:通过 Freemarker 配置加载
document.ftl
模板。 - 渲染模板:将数据模型渲染到模板,生成字节数组。
- 创建 Word 文档:使用 Apache POI 将渲染后的内容写入
.docx
文件。 - 输出文件:将生成的 Word 文件保存为
output.docx
。
5. 生成 PDF 文件
public void generatePDF() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.html.ftl");
Map<String, Object> data = getDataModel();
// 渲染模板到字符串
StringWriter stringWriter = new StringWriter();
template.process(data, stringWriter);
String htmlContent = stringWriter.toString();
// 使用 iText 将 HTML 转换为 PDF
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
document.open();
// 简单地将 HTML 作为段落添加,复杂的 HTML 需要使用 XMLWorker
document.add(new com.itextpdf.text.Paragraph(htmlContent));
document.close();
System.out.println("PDF 文件生成成功!");
} catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
e.printStackTrace();
}
}
解释:
- 加载模板:通过 Freemarker 配置加载
document.html.ftl
模板。 - 渲染模板:将数据模型渲染到模板,生成 HTML 字符串。
生成 PDF:使用 iText 将 HTML 内容写入 PDF 文件。
- 注意:iText 直接将 HTML 作为段落添加较为简单,复杂的 HTML 需要使用 iText 的 XMLWorker 或其他扩展库进行解析。
- 输出文件:将生成的 PDF 文件保存为
output.pdf
。
6. 主类实现
public class DocumentGenerator {
public static void main(String[] args) {
DocumentGenerator generator = new DocumentGenerator();
generator.generateWord();
generator.generatePDF();
}
// 以上所有方法...
}
解释:
- 主方法调用
generateWord
和generatePDF
方法,生成 Word 和 PDF 文件。
五、示例运行结果
执行 DocumentGenerator
主类后,将在项目根目录下生成 output.docx
和 output.pdf
两个文件,内容如下:
1. Word 文件 (output.docx
)
姓名:张三
年龄:30
职位:软件工程师
2. PDF 文件 (output.pdf
)
解释:
- Word 文件内容直接由模板填充。
- PDF 文件内容通过渲染后的 HTML 生成,外观可根据模板样式调整。
六、优化与高级用法 🌟
1. 使用 XMLWorker 解析复杂 HTML
iText 的 XMLWorker 允许解析复杂的 HTML 内容,保持模板的样式和结构。
添加 XMLWorker 依赖
在 pom.xml
中添加:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13.3</version>
</dependency>
修改 PDF 生成方法
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
public void generatePDF() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.html.ftl");
Map<String, Object> data = getDataModel();
// 渲染模板到字符串
StringWriter stringWriter = new StringWriter();
template.process(data, stringWriter);
String htmlContent = stringWriter.toString();
// 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new StringReader(htmlContent));
document.close();
System.out.println("PDF 文件生成成功!");
} catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
e.printStackTrace();
}
}
解释:
- XMLWorkerHelper:解析 HTML 内容,并保持样式,将其转换为 PDF 格式。
- 改进效果:PDF 文件将更好地反映模板中的样式和结构。
2. 动态模板路径
根据不同的业务需求,可以动态选择模板文件,实现多样化的文档生成。
public void generateWord(String templateName, String outputPath, Map<String, Object> data) {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate(templateName);
// 渲染模板到输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(baos);
template.process(data, out);
out.flush();
// 将渲染后的内容写入 Word 文档
InputStream is = new ByteArrayInputStream(baos.toByteArray());
XWPFDocument document = new XWPFDocument(is);
FileOutputStream fos = new FileOutputStream(outputPath);
document.write(fos);
fos.close();
document.close();
System.out.println("Word 文件生成成功!");
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
解释:
- 模板选择:通过参数传递模板名称,实现动态选择模板。
- 输出路径:通过参数指定生成文件的路径,增强方法的灵活性。
3. 增强数据模型
数据模型不仅限于简单的键值对,还可以包含列表、嵌套对象等,满足复杂文档需求。
示例:包含列表的模板
Word 模板 (document.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>姓名:${name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>项目列表:</w:t>
</w:r>
</w:p>
<#list projects as project>
<w:p>
<w:r>
<w:t>- ${project}</w:t>
</w:r>
</w:p>
</#list>
</w:body>
</w:document>
数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 30);
data.put("position", "软件工程师");
data.put("projects", Arrays.asList("项目A", "项目B", "项目C"));
return data;
}
解释:
- 列表渲染:使用
<#list>
指令遍历projects
列表,生成多个项目条目。 - 动态内容:根据数据模型中的列表内容,自动生成文档内容。
4. 模板复用与继承
Freemarker 支持模板继承,便于复用公共部分,提升模板管理效率。
示例:基模板与子模板
基模板 (base.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>${title}</w:t>
</w:r>
</w:p>
<#block content>
<!-- 子模板内容 -->
</#block>
</w:body>
</w:document>
子模板 (document.ftl
)
<#extends "base.ftl">
<#block content>
<w:p>
<w:r>
<w:t>姓名:${name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>年龄:${age}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>职位:${position}</w:t>
</w:r>
</w:p>
</#block>
解释:
- 模板继承:子模板通过
<#extends>
指令继承基模板,实现公共部分的复用。 - 块定义:基模板定义
<#block content>
,子模板在此块中填充具体内容。
七、图表与流程图辅助理解
1. 文档生成流程图 📈
flowchart TD;
A[开始] --> B[准备数据模型]
B --> C[加载 Freemarker 模板]
C --> D{生成 Word 或 PDF}
D -->|Word| E[使用 Apache POI 写入 .docx]
D -->|PDF| F[使用 iText 生成 .pdf]
E --> G[保存 Word 文件]
F --> G[保存 PDF 文件]
G --> H[结束]
解释:
- 开始:初始化文档生成流程。
- 准备数据模型:定义需要填充到模板中的数据。
- 加载 Freemarker 模板:根据需求加载相应的模板文件。
- 生成 Word 或 PDF:选择生成 Word 或 PDF 文件。
- 使用 Apache POI 或 iText:分别使用不同的库生成对应格式的文件。
- 保存文件:将生成的文件保存到指定路径。
- 结束:完成文档生成流程。
解释:
- 数据模型:包含文档生成所需的所有数据。
- Freemarker 模板:定义文档的结构和样式。
- 渲染后的内容:将数据模型填充到模板中,生成最终内容。
- 写入 Word 文件:使用 Apache POI 将内容写入
.docx
文件。 - 转换为 PDF 文件:使用 iText 将内容转换为
.pdf
文件。
3. 依赖关系表 📊
组件 | 功能 | 依赖 |
---|---|---|
Freemarker | 模板渲染,生成文档内容 | 无 |
Apache POI | 操作 Word 文件,生成 .docx | Freemarker, Java IO |
iText | 生成 PDF 文件 | Freemarker, Java IO |
Java IO | 文件读写操作 | Java |
数据模型 | 提供动态填充的数据 | Java 集合框架 |
模板文件 | 定义文档的结构和样式 | Freemarker |
解释:
- Freemarker:核心模板引擎,无需其他依赖即可运行。
- Apache POI:依赖 Freemarker 渲染后的内容,进行 Word 文件的操作。
- iText:依赖 Freemarker 渲染后的内容,进行 PDF 文件的生成。
- Java IO:基础文件操作工具,支撑所有文件读写任务。
- 数据模型:通过 Java 集合框架构建,提供模板渲染所需的数据。
八、常见问题与解决方案 🛠️
1. 模板渲染错误
问题描述:在渲染模板时,出现 TemplateException
,提示无法找到变量或语法错误。
解决方案:
- 检查变量名:确保数据模型中的变量名与模板中的占位符一致。
- 验证模板语法:检查模板文件的 Freemarker 语法是否正确。
- 调试数据模型:输出数据模型内容,确认数据是否正确传递。
示例:调试数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 30);
data.put("position", "软件工程师");
data.put("projects", Arrays.asList("项目A", "项目B", "项目C"));
// 输出数据模型,便于调试
data.forEach((k, v) -> System.out.println(k + ": " + v));
return data;
}
解释:
- 输出数据模型:在控制台打印所有变量,确认变量值是否正确。
2. Word 文件样式混乱
问题描述:生成的 Word 文件样式与模板设计不符,布局错乱。
解决方案:
- 验证模板结构:确保模板文件的 XML 结构符合 Word 的格式要求。
- 使用专业工具编辑模板:建议使用 Microsoft Word 等工具设计并保存模板,避免手动编写错误。
- 检查数据填充:确认填充的数据不会破坏模板的布局,例如避免过长的文本导致行溢出。
示例:使用模板编辑器
- 使用 Microsoft Word 创建模板文件,并保存为
.docx
格式。 - 将模板文件转换为 Freemarker 模板,保留占位符。
3. PDF 文件生成不完整
问题描述:生成的 PDF 文件内容不完整,样式缺失。
解决方案:
- 使用 XMLWorker:iText 直接添加 HTML 内容可能无法正确解析复杂的样式,建议使用 XMLWorker 解析 HTML。
- 检查 HTML 模板:确保 HTML 模板的结构和样式正确,避免语法错误。
- 调试 PDF 生成过程:输出渲染后的 HTML 内容,确认内容是否完整。
示例:使用 XMLWorker 解析 HTML
public void generatePDF() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.html.ftl");
Map<String, Object> data = getDataModel();
// 渲染模板到字符串
StringWriter stringWriter = new StringWriter();
template.process(data, stringWriter);
String htmlContent = stringWriter.toString();
// 输出渲染后的 HTML,用于调试
System.out.println("渲染后的 HTML 内容:\n" + htmlContent);
// 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new StringReader(htmlContent));
document.close();
System.out.println("PDF 文件生成成功!");
} catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
e.printStackTrace();
}
}
解释:
- 输出 HTML 内容:在控制台打印渲染后的 HTML,确认内容是否完整。
- 使用 XMLWorker:确保 HTML 内容被正确解析并转换为 PDF。
4. 字体显示不正确
问题描述:生成的 Word 或 PDF 文件中,中文字体显示不正确或乱码。
解决方案:
- 设置正确的编码:确保模板文件和 Java 代码使用
UTF-8
编码。 - 嵌入字体:在生成 PDF 时,嵌入支持中文的字体,避免字体缺失导致乱码。
- 检查操作系统字体:确保运行环境中已安装所需的中文字体。
示例:嵌入字体到 PDF
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;
public void generatePDF() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.html.ftl");
Map<String, Object> data = getDataModel();
// 渲染模板到字符串
StringWriter stringWriter = new StringWriter();
template.process(data, stringWriter);
String htmlContent = stringWriter.toString();
// 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
document.open();
// 设置支持中文的字体
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
Font font = new Font(bf, 12, Font.NORMAL);
com.itextpdf.text.Paragraph paragraph = new com.itextpdf.text.Paragraph(htmlContent, font);
document.add(paragraph);
document.close();
System.out.println("PDF 文件生成成功!");
} catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
e.printStackTrace();
}
}
解释:
- 嵌入中文字体:使用 iText 提供的
BaseFont
类加载支持中文的字体文件,确保 PDF 中中文显示正常。 - 应用字体:将加载的字体应用到 PDF 内容中。
九、扩展功能与高级应用
1. 表格生成
Freemarker 可以动态生成包含表格的文档,满足复杂数据展示需求。
示例:生成包含表格的 Word 文档
Word 模板 (document.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>用户列表</w:t>
</w:r>
</w:p>
<w:tbl>
<w:tr>
<w:tc>
<w:p><w:r><w:t>姓名</w:t></w:r></w:p>
</w:tc>
<w:tc>
<w:p><w:r><w:t>年龄</w:t></w:r></w:p>
</w:tc>
<w:tc>
<w:p><w:r><w:t>职位</w:t></w:r></w:p>
</w:tc>
</w:tr>
<#list users as user>
<w:tr>
<w:tc>
<w:p><w:r><w:t>${user.name}</w:t></w:r></w:p>
</w:tc>
<w:tc>
<w:p><w:r><w:t>${user.age}</w:t></w:r></w:p>
</w:tc>
<w:tc>
<w:p><w:r><w:t>${user.position}</w:t></w:r></w:p>
</w:tc>
</w:tr>
</#list>
</w:tbl>
</w:body>
</w:document>
数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
data.put("users", Arrays.asList(
createUser("张三", 30, "软件工程师"),
createUser("李四", 28, "产品经理"),
createUser("王五", 35, "测试工程师")
));
return data;
}
private Map<String, Object> createUser(String name, int age, String position) {
Map<String, Object> user = new HashMap<>();
user.put("name", name);
user.put("age", age);
user.put("position", position);
return user;
}
解释:
- 列表渲染:通过
<#list>
指令遍历users
列表,动态生成表格行。 - 数据模型:定义包含多个用户信息的列表,满足表格生成需求。
2. 条件渲染
根据数据模型中的条件,动态控制文档内容的显示与隐藏。
示例:根据用户角色显示不同内容
Word 模板 (document.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>用户信息</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>姓名:${name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>年龄:${age}</w:t>
</w:r>
</w:p>
<#if role == "admin">
<w:p>
<w:r>
<w:t>角色:管理员</w:t>
</w:r>
</w:p>
</#if>
<#if role == "user">
<w:p>
<w:r>
<w:t>角色:普通用户</w:t>
</w:r>
</w:p>
</#if>
</w:body>
</w:document>
数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 30);
data.put("role", "admin"); // 可变更为 "user"
return data;
}
解释:
- 条件判断:使用
<#if>
指令,根据role
变量的值,动态显示不同的内容。
3. 复杂样式与布局
结合 Freemarker 和模板引擎,生成具有复杂样式和布局的文档。
示例:多级标题与段落
Word 模板 (document.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>${title}</w:t>
</w:r>
</w:p>
<#list sections as section>
<w:p>
<w:r>
<w:t>${section.header}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>${section.content}</w:t>
</w:r>
</w:p>
</#list>
</w:body>
</w:document>
数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
data.put("title", "项目报告");
data.put("sections", Arrays.asList(
createSection("项目概述", "这是项目的概述内容。"),
createSection("技术栈", "项目使用了 Java, Freemarker, Apache POI 等技术。"),
createSection("成果展示", "项目取得了显著的成果,具体如下。")
));
return data;
}
private Map<String, Object> createSection(String header, String content) {
Map<String, Object> section = new HashMap<>();
section.put("header", header);
section.put("content", content);
return section;
}
解释:
- 多级内容:通过列表渲染多个章节,每个章节包含标题和内容。
- 数据模型:定义多个章节的标题和内容,满足复杂文档结构需求。
十、最佳实践与优化建议 🔧
1. 模板设计规范
- 保持模板简洁:避免在模板中嵌入过多逻辑,保持模板的可读性和可维护性。
- 使用模板继承:利用 Freemarker 的模板继承功能,复用公共部分,减少冗余。
- 分离样式与内容:尽量将样式与内容分离,通过 CSS 或 Word 样式定义文档外观。
2. 数据模型设计
- 结构化数据:使用嵌套的 Map 和 List,确保数据模型与模板结构对应。
- 数据验证:在渲染前验证数据模型,确保所有必需的数据都已提供。
- 动态数据加载:根据需要,动态加载数据模型中的部分内容,提升灵活性。
3. 性能优化
缓存模板:Freemarker 支持模板缓存,避免重复加载,提高渲染效率。
cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 100));
- 优化数据处理:在渲染大数据模型时,优化数据处理逻辑,避免内存溢出。
- 异步处理:对于耗时的文档生成任务,考虑使用异步处理机制,提升应用响应速度。
4. 错误处理
捕获异常:在文档生成过程中,捕获并处理所有可能的异常,确保应用稳定性。
try { // 文档生成逻辑 } catch (IOException | TemplateException | DocumentException e) { // 记录错误日志 e.printStackTrace(); // 提示用户或采取其他措施 }
- 日志记录:使用日志框架记录关键操作和错误信息,便于后续调试和维护。
5. 安全性考虑
- 防止模板注入:确保模板文件的来源可信,避免恶意模板注入攻击。
- 敏感数据保护:在数据模型中处理敏感数据时,确保数据的加密和安全传输。
十一、实用示例与代码解析
示例 1:批量生成用户报告
假设需要为多个用户生成个人报告,可以通过循环调用实现。
数据模型
private Map<String, Object> getDataModel() {
Map<String, Object> data = new HashMap<>();
List<Map<String, Object>> users = Arrays.asList(
createUser("张三", 30, "软件工程师"),
createUser("李四", 28, "产品经理"),
createUser("王五", 35, "测试工程师")
);
data.put("users", users);
return data;
}
Word 模板 (document.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<#list users as user>
<w:p>
<w:r>
<w:t>姓名:${user.name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>年龄:${user.age}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>职位:${user.position}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>------------------------</w:t>
</w:r>
</w:p>
</#list>
</w:body>
</w:document>
代码实现
public void generateBatchWord() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.ftl");
Map<String, Object> data = getDataModel();
// 渲染模板到输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(baos);
template.process(data, out);
out.flush();
// 将渲染后的内容写入 Word 文档
InputStream is = new ByteArrayInputStream(baos.toByteArray());
XWPFDocument document = new XWPFDocument(is);
FileOutputStream fos = new FileOutputStream("batch_output.docx");
document.write(fos);
fos.close();
document.close();
System.out.println("批量 Word 文件生成成功!");
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
解释:
- 循环渲染:通过
<#list>
指令遍历users
列表,为每个用户生成一段内容。 - 批量输出:将所有用户的内容写入同一个 Word 文档,使用分隔符区分不同用户。
示例 2:生成带有图表的 PDF 报告
在 PDF 报告中添加图表,可以通过生成图像并嵌入到 HTML 模板中实现。
数据模型
private Map<String, Object> getDataModelWithChart() {
Map<String, Object> data = new HashMap<>();
data.put("title", "销售报告");
data.put("summary", "本季度销售情况如下:");
data.put("salesData", Arrays.asList(
createSales("一月", 15000),
createSales("二月", 20000),
createSales("三月", 18000)
));
return data;
}
private Map<String, Object> createSales(String month, int amount) {
Map<String, Object> sale = new HashMap<>();
sale.put("month", month);
sale.put("amount", amount);
return sale;
}
HTML 模板 (document.html.ftl
)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${title}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>${title}</h1>
<p>${summary}</p>
<canvas id="salesChart" width="400" height="200"></canvas>
<script>
var ctx = document.getElementById('salesChart').getContext('2d');
var salesChart = new Chart(ctx, {
type: 'bar',
data: {
labels: [
<#list salesData as sale>
"${sale.month}"<#if sale_has_next>,</#if>
</#list>
],
datasets: [{
label: '销售额',
data: [
<#list salesData as sale>
${sale.amount}<#if sale_has_next>,</#if>
</#list>
],
backgroundColor: 'rgba(54, 162, 235, 0.6)'
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
</body>
</html>
解释:
- Chart.js:使用 Chart.js 库生成图表。
- 数据填充:通过
<#list>
指令动态填充图表的标签和数据。 - 图表展示:在 PDF 中展示生成的图表。
代码实现
import com.itextpdf.text.Image;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
public void generatePDFWithChart() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.html.ftl");
Map<String, Object> data = getDataModelWithChart();
// 渲染模板到字符串
StringWriter stringWriter = new StringWriter();
template.process(data, stringWriter);
String htmlContent = stringWriter.toString();
// 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("sales_report.pdf"));
document.open();
// 配置字体,支持中文
XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
fontProvider.register("STSong-Light");
HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
htmlContext.setFontProvider(fontProvider);
// 解析 HTML 内容
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new StringReader(htmlContent));
document.close();
System.out.println("带图表的 PDF 文件生成成功!");
} catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
e.printStackTrace();
}
}
解释:
- 渲染 HTML:将数据模型填充到 HTML 模板中,生成包含图表的 HTML 内容。
- 解析 HTML:使用 XMLWorker 解析 HTML,并将其转换为 PDF 格式。
- 嵌入图表:通过 Chart.js 生成的图表作为图像嵌入 PDF 文件中。
示例 3:动态选择模板生成不同类型文档
根据不同的业务需求,动态选择模板文件,生成不同类型的文档。
数据模型
private Map<String, Object> getDataModelForDynamicTemplate(String docType) {
Map<String, Object> data = new HashMap<>();
if ("report".equals(docType)) {
data.put("title", "项目报告");
data.put("content", "这是项目报告的详细内容。");
} else if ("invoice".equals(docType)) {
data.put("invoiceNumber", "INV-1001");
data.put("date", "2024-04-27");
data.put("amount", 5000);
}
return data;
}
Word 模板 (report.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>${title}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>${content}</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
Word 模板 (invoice.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>发票号码:${invoiceNumber}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>日期:${date}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>金额:${amount} 元</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
代码实现
public void generateDynamicDocument(String docType) {
try {
Configuration cfg = getFreemarkerConfiguration();
String templateName;
String outputPath;
if ("report".equals(docType)) {
templateName = "report.ftl";
outputPath = "project_report.docx";
} else if ("invoice".equals(docType)) {
templateName = "invoice.ftl";
outputPath = "invoice.docx";
} else {
System.out.println("未知的文档类型!");
return;
}
Template template = cfg.getTemplate(templateName);
Map<String, Object> data = getDataModelForDynamicTemplate(docType);
// 渲染模板到输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(baos);
template.process(data, out);
out.flush();
// 将渲染后的内容写入 Word 文档
InputStream is = new ByteArrayInputStream(baos.toByteArray());
XWPFDocument document = new XWPFDocument(is);
FileOutputStream fos = new FileOutputStream(outputPath);
document.write(fos);
fos.close();
document.close();
System.out.println(docType + " 文档生成成功!");
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
解释:
- 动态选择模板:根据传入的
docType
参数,选择不同的模板文件和输出路径。 - 通用生成逻辑:使用相同的方法渲染不同模板,生成对应的 Word 文件。
示例 4:生成带有图片的文档
在文档中嵌入图片,可以通过 Freemarker 和 Apache POI 实现。
数据模型
private Map<String, Object> getDataModelWithImage() {
Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 30);
data.put("position", "软件工程师");
data.put("photoPath", "src/main/resources/images/photo.jpg"); // 图片路径
return data;
}
Word 模板 (document_with_image.ftl
)
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>姓名:${name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>年龄:${age}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>职位:${position}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>照片:</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:drawing>
<wp:inline>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="0" name=""/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId1"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="990000" cy="792000"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
</w:body>
</w:document>
解释:
- 图片嵌入:使用 Word 的 XML 结构,将图片嵌入到文档中。
- 占位符:
rId1
是图片的引用标识符,在后续操作中需要关联实际的图片文件。
代码实现
public void generateWordWithImage() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document_with_image.ftl");
Map<String, Object> data = getDataModelWithImage();
// 渲染模板到输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(baos);
template.process(data, out);
out.flush();
// 读取渲染后的内容
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
XWPFDocument document = new XWPFDocument(bais);
// 添加图片
String imgFile = (String) data.get("photoPath");
InputStream pic = new FileInputStream(imgFile);
document.addPictureData(pic, Document.PICTURE_TYPE_JPEG);
pic.close();
// 获取图片关系 ID
String picId = document.getAllPictures().get(0).getPackageRelationship().getId();
// 替换模板中的图片引用 ID
for (XWPFParagraph para : document.getParagraphs()) {
for (XWPFRun run : para.getRuns()) {
String text = run.getText(0);
if (text != null && text.contains("rId1")) {
text = text.replace("rId1", picId);
run.setText(text, 0);
}
}
}
// 保存 Word 文件
FileOutputStream fos = new FileOutputStream("output_with_image.docx");
document.write(fos);
fos.close();
document.close();
System.out.println("带图片的 Word 文件生成成功!");
} catch (IOException | TemplateException | org.apache.poi.openxml4j.exceptions.InvalidFormatException e) {
e.printStackTrace();
}
}
解释:
- 渲染模板:将数据模型填充到模板中,生成包含图片引用的 Word 文档。
- 添加图片:使用 Apache POI 将实际的图片文件添加到文档中。
- 替换图片引用 ID:将模板中的图片引用
rId1
替换为实际的关系 ID,确保图片正确显示。 - 保存文件:将生成的 Word 文件保存为
output_with_image.docx
。
示例 5:自动化批量生成报告
结合上述功能,可以实现批量生成多个用户的报告,并保存为不同的文件。
代码实现
public void generateBatchReports() {
try {
Configuration cfg = getFreemarkerConfiguration();
Template template = cfg.getTemplate("document.ftl");
List<Map<String, Object>> users = Arrays.asList(
createUser("张三", 30, "软件工程师"),
createUser("李四", 28, "产品经理"),
createUser("王五", 35, "测试工程师")
);
for (Map<String, Object> user : users) {
// 设置当前用户数据
Map<String, Object> data = new HashMap<>();
data.putAll(user);
// 渲染模板到输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(baos);
template.process(data, out);
out.flush();
// 将渲染后的内容写入 Word 文档
InputStream is = new ByteArrayInputStream(baos.toByteArray());
XWPFDocument document = new XWPFDocument(is);
String outputPath = user.get("name") + "_report.docx";
FileOutputStream fos = new FileOutputStream(outputPath);
document.write(fos);
fos.close();
document.close();
System.out.println(outputPath + " 生成成功!");
}
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
解释:
- 循环生成:遍历用户列表,为每个用户生成单独的 Word 报告文件。
- 动态输出路径:根据用户姓名,生成对应的报告文件名,避免文件覆盖。
十二、图表与流程图辅助理解
1. 文档生成详细流程图 📊
graph TD;
A[开始] --> B[准备数据模型]
B --> C[加载 Freemarker 模板]
C --> D{选择生成类型}
D -->|Word| E[渲染模板]
D -->|PDF| F[渲染 HTML 模板]
E --> G[使用 Apache POI 写入 .docx]
F --> H[使用 iText 生成 .pdf]
G --> I[保存 Word 文件]
H --> I[保存 PDF 文件]
I --> J[结束]
解释:
- 选择生成类型:根据需求选择生成 Word 或 PDF 文件。
- 不同路径处理:分别使用 Apache POI 和 iText 处理不同格式的文档生成。
3. 依赖关系图表 📋
组件 | 功能 | 依赖 |
---|---|---|
Freemarker | 模板渲染,生成文档内容 | Java |
Apache POI | 操作 Word 文件,生成 .docx | Freemarker, Java IO |
iText | 生成 PDF 文件 | Freemarker, Java IO, XMLWorker |
Java IO | 文件读写操作 | Java |
数据模型 | 提供动态填充的数据 | Java 集合框架 |
模板文件 | 定义文档的结构和样式 | Freemarker |
XMLWorker | 解析 HTML,支持复杂样式 | iText |
解释:
- 组件关系:展示各组件之间的依赖和功能分工,帮助理解整体架构。
十三、总结 🎯
通过本文的详细指南,您已经掌握了如何使用 Java 与 Freemarker 生成 Word 和 PDF 文件的全流程。从环境配置、模板设计,到代码实现和优化技巧,每一步都进行了深入解析。以下是关键要点总结:
- Freemarker 模板引擎:灵活高效地填充数据模型,生成动态文档内容。
- Apache POI:强大的 Word 操作库,支持复杂的文档生成需求。
- iText:功能丰富的 PDF 生成库,通过 XMLWorker 支持复杂的样式和布局。
- 数据模型设计:结构化、灵活的数据模型是成功生成文档的基础。
- 模板设计规范:简洁、可复用的模板设计提升开发效率和文档质量。
- 优化与最佳实践:合理的错误处理、性能优化和安全性考虑,确保文档生成过程稳定可靠。
通过持续应用和优化这些方法,您可以高效地在 Java 项目中实现自动化的 Word 和 PDF 文档生成,满足各种业务需求,提升开发与运维效率。🚀🔍
参考图表
1. 文档生成详细流程图
graph TD;
A[开始] --> B[准备数据模型]
B --> C[加载 Freemarker 模板]
C --> D{选择生成类型}
D -->|Word| E[渲染模板]
D -->|PDF| F[渲染 HTML 模板]
E --> G[使用 Apache POI 写入 .docx]
F --> H[使用 iText 生成 .pdf]
G --> I[保存 Word 文件]
H --> I[保存 PDF 文件]
I --> J[结束]
3. 依赖关系图表
组件 | 功能 | 依赖 |
---|---|---|
Freemarker | 模板渲染,生成文档内容 | Java |
Apache POI | 操作 Word 文件,生成 .docx | Freemarker, Java IO |
iText | 生成 PDF 文件 | Freemarker, Java IO, XMLWorker |
Java IO | 文件读写操作 | Java |
数据模型 | 提供动态填充的数据 | Java 集合框架 |
模板文件 | 定义文档的结构和样式 | Freemarker |
XMLWorker | 解析 HTML,支持复杂样式 | iText |
解释:
- 组件关系:展示各组件之间的依赖和功能分工,帮助理解整体架构。
4. 模板与数据驱动测试优势图解
flowchart TD
A[数据驱动测试] --> B[使用外部数据文件]
A --> C[简化循环逻辑]
D[脚本控制循环] --> E[根据响应动态控制]
D --> F[灵活应对复杂条件]
B --> G[批量处理数据]
E --> G
解释:
- 数据驱动测试:通过外部数据文件批量处理数据,简化循环逻辑。
- 脚本控制循环:根据响应动态控制循环,灵活应对复杂条件。
- 共同优势:高效批量处理数据,提升测试效率。
结束语
Freemarker 结合 Java 的强大生态系统,为文档生成提供了高效、灵活的解决方案。无论是生成简单的文本报告,还是复杂的带有图表和图片的 PDF 文件,Freemarker 都能满足各种需求。通过本文的详细解析,您已经掌握了从环境配置、模板设计到代码实现的全流程。持续学习和实践,将助您在自动化文档生成的道路上不断前进,提升工作效率和文档质量。💪🚀
结束
通过全面解析 Java 使用 Freemarker 生成 Word 和 PDF 文件的方法,结合实际示例和图表说明,您可以更加高效地实现自动化文档生成,满足复杂的业务需求,提升开发与运维效率。📈🔧