在日常开发中,我们经常遇到需要生成复杂报表的场景。比如每周的工作报告、电商平台的订单详情、数据统计报表等。这些文档往往包含大量结构化数据,而且格式要求多变:有的需要合并单元格,有的需要根据数值显示不同颜色,还有的需要嵌套子列表。
传统做法是直接操作POI API,但这种方式有几个明显痛点:代码臃肿难以维护、样式与数据耦合严重、复用性差。我曾经接手过一个老项目,光是调整报表格式就要修改几十处硬编码,每次改动都提心吊胆。
XWPFTemplate提供的策略模式正好解决了这些问题。通过将渲染逻辑封装成独立策略,我们可以实现:
首先确保项目中已引入poi-tl依赖。建议使用最新稳定版,我在1.10.3版本上实测效果最好:
xml复制<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.3</version>
</dependency>
基础模板文档建议使用.docx格式,用Word提前设计好表格框架。一个小技巧:在模板中用{{#details}}标记表格区域,这个标签名后续会与Java代码中的绑定键对应。
处理动态表格时,数据结构设计直接影响渲染效率。推荐使用List<Map<String, Object>>结构,例如:
java复制List<Map<String,Object>> detailList = new ArrayList<>();
Map<String,Object> row1 = new HashMap<>();
row1.put("productName", "MacBook Pro");
row1.put("price", 12999);
row1.put("stock", 15);
detailList.add(row1);
这种结构的好处是:
核心代码结构如下,我们以周报表格为例:
java复制// 初始化配置
ConfigureBuilder builder = Configure.builder();
// 绑定策略
HackLoopTableRenderPolicy policy = new HackLoopTableRenderPolicy();
builder.bind("weeklyReport", policy);
// 准备数据
Map<String, Object> data = new HashMap<>();
List<Map<String, Object>> weeklyData = getWeeklyData(); // 获取业务数据
data.put("weeklyReport", weeklyData);
// 渲染文档
XWPFTemplate.compile("template.docx", builder.build())
.render(data)
.writeToFile("output.docx");
这里有几个关键点:
bind方法的第一个参数必须与模板中的标签名一致实际项目中我们往往需要更复杂的功能。比如这个电商订单案例:
java复制public class OrderTablePolicy extends HackLoopTableRenderPolicy {
@Override
public void render(TableRenderData table, Object data) {
List<Map<String, Object>> orders = (List<Map<String, Object>>) data;
// 动态调整列宽
table.setWidth(8000);
// 条件格式设置
for (Map<String, Object> order : orders) {
double amount = (double) order.get("amount");
if(amount > 10000) {
order.put("rowColor", "FF0000"); // 高金额标红
}
}
super.render(table, orders);
}
}
通过继承基础策略类,我们可以实现:
遇到需要表内套表的情况(比如订单包含多个商品),可以采用分级渲染策略:
java复制// 主表策略
builder.bind("orders", new OrderTablePolicy());
// 子表策略
builder.bind("items", new ItemTablePolicy());
// 数据结构示例
Map<String, Object> order1 = new HashMap<>();
order1.put("orderNo", "202308001");
order1.put("items", Arrays.asList(
itemMap1, itemMap2, itemMap3
));
模板设计时需要注意:
通过自定义策略可以灵活控制样式。比如这个根据库存量变色的例子:
java复制public class InventoryPolicy extends HackLoopTableRenderPolicy {
private static final String LOW_STOCK_COLOR = "FFFF00";
private static final String NORMAL_COLOR = "FFFFFF";
@Override
public void render(TableRenderData table, Object data) {
List<Map<String, Object>> inventoryList = (List<Map<String, Object>>) data;
inventoryList.forEach(item -> {
int stock = (int) item.get("stock");
if(stock < 10) {
item.put("cellColor", LOW_STOCK_COLOR);
item.put("warning", "库存不足");
} else {
item.put("cellColor", NORMAL_COLOR);
}
});
super.render(table, inventoryList);
}
}
处理大型表格时容易遇到OOM问题,我总结了几条经验:
java复制try (XWPFTemplate template = XWPFTemplate.compile("template.docx", config)) {
template.render(data);
template.writeToFile("output.docx");
} // 自动关闭资源
调试时如果遇到表格渲染异常,建议按这个顺序检查:
一个实用的调试技巧:先用简单数据测试基本渲染,再逐步增加复杂度。我在处理一个合并单元格bug时,就是通过这种方式发现是数据中存在null值导致的格式错乱。