flexcel 是一个基于 Apache POI 构建的高性能、可扩展的流式 Excel 模板引擎。它专为大规模数据导出场景设计,渲染性能出色。
插件化的语法架构允许开发者通过自定义处理器轻松扩展模板语法,实现高度灵活和定制化的 Excel 报表生成。
- 🚀 高性能流式处理: 基于
SXSSFWorkbook,支持大规模数据导出,内存占用稳定且可控。 - ⚡ 预编译模板: 在渲染前将模板编译为内部抽象语法树(AST),极大提升了循环和条件判断的执行效率。
- 🔌 完全插件化:
- 单元格语法: 通过实现
CellSyntaxHandler接口,可轻松添加如#image,#link等自定义单元格语法,并定义其渲染行为。 - ...
- 单元格语法: 通过实现
- 🎨 样式全继承: 完美保留模板中的所有样式、行高、列宽、合并单元格等格式。
- 🔧 功能强大的默认语法: 内置
#if/#else/#end,#foreach,${!var}(纵向合并),#formula(公式) 等常用指令。 - 🌐 轻量级依赖: 核心功能仅依赖
poi-ooxml。Spring Expression Language (SpEL)作为可选依赖,便于无缝集成到任何现代Java应用中。
将以下配置添加到您的 pom.xml 中:
<dependency>
<groupId>com.github.jwj</groupId>
<artifactId>flexcel</artifactId>
<version>0.1.1</version>
</dependency>
<!-- 您的项目中需要提供以下运行时依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<!-- 以及一个 SLF4J 的具体实现,如 logback 或 log4j2 -->创建一个名为 template.xlsx 的模板文件。
| A | B | C | D |
|---|---|---|---|
| 报表名称 | 产品销售报表 | 制表人 | ${operator.name} |
| 类别 | 产品名称 | 价格 | 库存状态 |
#foreach item in ${productList} |
|||
${!item.category} |
${item.name} |
${item.getFormattedPrice()} |
${item.stock > 10 ? '充足' : (item.stock > 0 ? '库存紧张' : '已售罄')} |
#end |
|||
| 合计 | #formula SUM(C3:C${endRowNo}) |
使用Builder模式创建和配置引擎实例。
import com.github.jwj.flexcel.engine.PoiTemplateEngine;
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
// 1. 使用 Builder 构建引擎实例
ExcelTemplateEngine engine = PoiTemplateEngine.builder()
.sxssfWindowSize(1000) // 设置 SXSSF 内存窗口大小
.queueCapacity(2048) // 设置队列容量
.build();
// 2. 准备数据模型
Map<String, Object> data = new HashMap<>();
data.put("operator", new Operator("Admin"));
List<Product> productList = createProductData(); // 自行实现数据准备
data.put("productList", productList);
// 3. 加载模板并执行渲染
try (InputStream templateStream = new FileInputStream("template.xlsx");
OutputStream outputStream = new FileOutputStream("report.xlsx")) {
engine.process(templateStream, data, outputStream);
}
System.out.println("报表生成成功: report.xlsx");
}
}使用resource\SalesReport.xlsx模板文件和测试源根下的FullFeatureTest类快速开始
- 标准表达式:
${expression}- 使用 Spring Expression Language (SpEL) 求值。
- 示例:
${user.name},${item.price * 1.2}
- 纯表达式: 如果单元格内容就是一个完整的
${...},引擎会返回表达式的实际类型(如Integer,Date),而非字符串。
- 默认安全模式: 引擎默认在安全的沙箱 (
SimpleEvaluationContext) 中运行 SpEL。在此模式下:- 禁止
T()类型表达式、new构造函数和任意类加载,杜绝了远程代码执行 (RCE) 的风险。 - 允许 访问 Map 的属性 (
${map.key})。 - 允许 访问 POJO 的公共属性 (
${pojo.property})。 - 允许 调用通过
registerService注册的服务的方法 (${myService.myMethod()})。
- 禁止
- 危险模式: 如果您完全信任模板的来源,并且确实需要使用
T()等高级 SpEL 功能,可以通过builder.enableUnsafeSpelOperations()开启。- 警告:开启此模式会带来严重的安全风险,请仅在绝对必要时使用。
- 循环:
#foreach item in ${collection}...#enditem: 循环变量名。collection: SpEL表达式,其结果必须是Iterable。- 内置变量:
index(从0开始的索引),currentRowNo(当前输出的Excel行号)。
- 条件判断:
#if ${condition}...#else...#endcondition: SpEL表达式,求值结果遵循布尔转换规则。#else块是可选的。
- 纵向合并:
${!variable}- 仅用于
#foreach循环内。当variable的值与上一行相同时,单元格会自动进行纵向合并。 - 但合并过多会严重影响性能,建议仅在必要时使用。
- 仅用于
- 公式:
#formula expressionexpression: 一个Excel公式字符串,可以包含变量。- 示例:
#formula SUM(C3:C${endRowNo}) - 内置变量:
#foreach循环结束后,会自动在上下文中注入startRowNo和endRowNo。
| 变量名 | 可用范围 | 描述 |
|---|---|---|
item |
#foreach 循环体内 |
循环的当前迭代项,名字由#foreach item in ... 定义。 |
index |
#foreach 循环体内 |
当前项在集合中的索引(从0开始)。 |
innerIndex |
#foreach 循环体内 |
index 的别名。 |
currentRowNo |
#foreach 和静态块内 |
当前正在生成的Excel绝对行号(从1开始)。 |
startRowNo |
#foreach 循环之后 |
刚刚结束的循环所生成的第一行的行号。 |
endRowNo |
#foreach 循环之后 |
刚刚结束的循环所生成的最后一行的行号。 |
printDate |
全局 | 模板处理开始时的时间 (java.util.Date)。 |
通过 Builder 模式,你可以轻松注册自定义的语法处理器来扩展引擎功能。
1. 实现 CellSyntaxHandler
public class QrCodeTextHandler implements CellSyntaxHandler {
private static final String PREFIX = "#qrcode_text ";
@Override
public boolean canHandle(String rawText) {
return rawText != null && rawText.trim().startsWith(PREFIX);
}
@Override
public CellTemplate handle(CellParseContext context) {
String expression = context.getRawStringValue().trim().substring(PREFIX.length());
String finalExpression = "'[QRCODE]: ' + (" + expression + ")";
return new DefaultCellTemplate(
context.getTemplateAddress(),
context.getColIndex(),
finalExpression, false, false);
}
}2. 注册插件
ExcelTemplateEngine engine = PoiTemplateEngine.builder()
.registerCellSyntaxHandler(new QrCodeTextHandler())
.build();引擎的插件系统不仅能处理值,还能定义复杂的渲染行为。
1. 实现一个能插入批注的 #comment 处理器
public static class CommentCellHandler implements CellSyntaxHandler {
private static final String PREFIX = "#comment ";
@Override
public boolean canHandle(String rawText) {
return rawText != null && rawText.trim().startsWith(PREFIX);
}
@Override
public CellTemplate handle(CellParseContext context) {
// 解析出表达式,它将作为批注的内容
String commentExpression = context.getRawStringValue().trim().substring(PREFIX.length());
testLogger.info("Inserted comment into cell at {}", commentExpression);
// 返回一个 CellTemplate,它在运行时会创建一个带有自定义渲染器的 RenderedCell
return (templateContext, pool, stringCache, evaluator) -> {
// 在运行时求值,获取批注的最终文本
Object commentValue = evaluator.evaluateString(commentExpression, templateContext.getAllData());
String commentText = (commentValue != null) ? commentValue.toString() : "";
// 创建自定义渲染动作
Consumer<Cell> renderer = (cell) -> {
// 这个 lambda 将在消费者线程中执行
Sheet sheet = cell.getSheet();
Drawing<?> drawing = sheet.getDrawingPatriarch();
if (drawing == null) {
drawing = sheet.createDrawingPatriarch();
}
CreationHelper factory = sheet.getWorkbook().getCreationHelper();
// 创建锚点
ClientAnchor anchor = factory.createClientAnchor();
anchor.setCol1(cell.getColumnIndex());
anchor.setRow1(cell.getRowIndex());
anchor.setCol2(cell.getColumnIndex() + 2); // 批注框大小
anchor.setRow2(cell.getRowIndex() + 3);
// 创建批注
Comment comment = drawing.createCellComment(anchor);
RichTextString str = factory.createRichTextString(commentText);
comment.setString(str);
comment.setAuthor("flexcel Engine");
// 将批注应用到单元格
cell.setCellComment(comment);
};
// 获取一个 RenderedCell 并设置自定义渲染器
RenderedCell renderedCell = pool.acquireCell();
renderedCell.setCustom(context.getTemplateAddress(), context.getColIndex(), renderer);
return renderedCell;
};
}
}2. 在模板中使用
| #comment 批注内容 |
# 克隆项目
git clone https://github.com/jwj/flexcel.git
cd flexcel
# 运行测试
mvn test
# 打包
mvn package本项目基于 MIT License 开源。