本文主要介绍easypoi导出Excel的代码示例;自己之前手动实现过导出工具类《编码技巧——导出工具类》,基于实体和注解,通过反射来映射实体字段和exce列的关系;在部分工程里面看到了easypoi的二方包,于是准备试用下,记录下如何引入并使用Easypoi的过程;
总的来说,easypoi基于Apache poi二次封装的开源二方包,用起来也比较简单,也提供了注解方式导出,并且在顺序、层级、颜色宽度、单元格合并等样式处理上,提供了丰富的注解属性和接口,推荐使用;
easypoi是为了让开发者快速的实现excel、word、pdf的导入导出,基于Apache poi基础上的一个工具包;功能如下:
(1)基于注解的导入导出,可以灵活定义的表头字段,修改注解就可以修改Excel;
(2)支持常用的样式自定义;
(3)支持一对多的导出/导入;
(4)支持模板的导出,一些常见的标签,自定义标签;
(5)支持HTML/Excel转换;
(6)支持word、图片、excel的导出;
因为本篇介绍的是使用easypoi注解方式的示例,先介绍下常用的注解;
1. @ExcelTarget
@ExcelTarget注解作用于最外层的对象,可以这么理解——这个类与一个excel对应(类属性对应excel的列);在使用easypoi的API导出excel生成Workbook对象时,作为参数传入,如下:
- /**
- * @description 导出实体类
- */
- @Data
- @ExcelTarget("auditFlowExport")
- public class AuditFlowExport {
- ...
- }
-
- // 调用easypoi的API生成excel对象
- final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);
2. @Excel
@Excel 注解是作用到Filed上面,是对Excel一列的一个描述,这个注解是必须要的注解,使用示例如下:
- /**
- * 一级审批人,excel列名:一级审批人、顺序6、宽度20、分组"审批人信息"
- */
- @Excel(name = "一级审批人", orderNum = "6", width = 20, groupName = "审批人信息")
- private String firstAuditor;
3. @ExcelEntity
@ExcelEntity注解表示一个继续深入导出的实体,是作用一个类型为实体的属性上面;
4. @ExcelCollection
@ExcelCollection注解表示一个集合,主要针对一对多的导出,作用在类型是List的属性上面;比如一个工单对应多个审批人,审批人就可以用集合表示如下:
- /**
- * 审核人信息
- */
- @ExcelCollection(name = "审核人信息", orderNum = "6")
- private List
stepAndAuditorList;
需求背景
将工单记录以Excel格式导出给审计人员,每条记录包括工单的基本信息;由于不同工单的审核层级不同,因此将审核人信息合并展示;
以下是接入及实现的步骤:
1. 引入依赖
当前工程为SpringBoot项目,引入以下依赖;
-
- <dependency>
- <groupId>cn.afterturngroupId>
- <artifactId>easypoi-spring-boot-starterartifactId>
- <version>4.0.0version>
- dependency>
需要注意的是由于easypoi的依赖内部依赖原生的poi,所以引入了easypoi的依赖之后,需要把原生的poi的依赖删掉,不然可能遇到类冲突;
2. 定义导出数据对应的类
通过注解方式定义字段的excel列名、宽度、字体等;因为里面存在一对多(List类型给的属性),因此其他字段的注解属性加了needMerge = true,表示合并单元格;
- import cn.afterturn.easypoi.excel.annotation.Excel;
- import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
- import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
- import lombok.Data;
-
- import java.util.Date;
- import java.util.List;
-
- /**
- * @author Akira
- * @description 导出实体类
- */
- @Data
- @ExcelTarget("auditFlowExport")
- public class AuditFlowExport {
-
- /**
- * 工单id
- */
- @Excel(name = "审批单号", orderNum = "1", needMerge = true)
- private Long flowId;
-
- /**
- * 工单标题
- */
- @Excel(name = "流程名称", orderNum = "2", width = 50, needMerge = true)
- private String title;
-
- /**
- * 发起人
- */
- @Excel(name = "发起人", orderNum = "3", width = 18, needMerge = true)
- private String submitorName;
-
- /**
- * 工单创建时间 格式yyyy-MM-dd HH:mm:ss
- */
- @Excel(name = "发起时间", orderNum = "4", exportFormat = "yyyy-MM-dd HH:mm:ss", width = 25, needMerge = true)
- private Date createTime;
-
- /**
- * 业务模块编码
- */
- @Excel(name = "归属项目", orderNum = "5", width = 25, needMerge = true)
- private String secondSubModuleCodeName;
-
- /**
- * 审核人信息 因为存在一对多,因此其他列属性定义needMerge = true 即合并单元格
- */
- @ExcelCollection(name = "审核人信息", orderNum = "6")
- private List
stepAndAuditorList; -
- /**
- * 详情信息
- */
- @Excel(name = "详情信息", orderNum = "7", width = 120, needMerge = true)
- private String displayText;
-
- @Data
- public static class StepAndAuditorExport {
- @Excel(name = "审核层级", orderNum = "1", width = 10)
- private int step;
- @Excel(name = "审核人", orderNum = "2", width = 18)
- private String auditor;
- }
- }
3. 定义导出格式
调用导出方法时,其中一个参数就是样式;easypoi已经默认了一套样式,有自定义样式需求时,支持实现原默认样式类来扩展样式;下面的样式比较简单,仅将表头的字体设置大一号且加粗;
- /**
- * @author Akira
- * @description 简单样式 仅统一字体
- */
- public class ExcelSimpleStyleUtil extends ExcelExportStylerDefaultImpl {
-
- public ExcelSimpleStyleUtil(Workbook workbook) {
- super(workbook);
- }
-
- /**
- * 这里设置表头的格式,最上面的一行
- * @see ExportParams#title
- */
- @Override
- public CellStyle getHeaderStyle(short color) {
- CellStyle cellStyle = super.getHeaderStyle(color);
- cellStyle.setFont(getFont(workbook, 11, true));
- cellStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.GREY_50_PERCENT.getIndex());
- return cellStyle;
- }
-
- /**
- * 列标题
- */
- @Override
- public CellStyle getTitleStyle(short color) {
- CellStyle cellStyle = super.getTitleStyle(color);
- // 仅将表头的字体设置大一号且加粗
- cellStyle.setFont(getFont(workbook, 11, true));
- cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_TURQUOISE.getIndex());
- return cellStyle;
- }
-
- /*以下都是行样式,交替*/
-
- /**
- * 行样式
- */
- @Override
- public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
- CellStyle cellStyle = super.stringSeptailStyle(workbook, isWarp);
- cellStyle.setFont(getFont(workbook, 10, false));
- return cellStyle;
- }
-
- /**
- * 这里设置循环行,没有样式的一行
- */
- @Override
- public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
- CellStyle cellStyle = super.stringNoneStyle(workbook, isWarp);
- cellStyle.setFont(getFont(workbook, 10, false));
- return cellStyle;
- }
-
- /**
- * 字体样式
- *
- * @param size 字体大小
- * @param isBold 是否加粗
- * @return
- */
- private Font getFont(Workbook workbook, int size, boolean isBold) {
- Font font = workbook.createFont();
- // 字体样式
- font.setFontName("微软雅黑");
- // 是否加粗
- font.setBold(isBold);
- // 字体大小
- font.setFontHeightInPoints((short) size);
- return font;
- }
-
- }
4. 导出方法
其核心就是调用easypoi封装好的ExcelExportUtil#exportExcel方法,返回Workbook对象,然后向response里面写字节流;
- /**
- * 导出
- *
- * @param response
- * @param pageQuery
- */
- public void export(HttpServletResponse response, AuditFlowPageQuery query) {
- // 查询结果并转换成导出类型AuditFlowExport
- final List
exportList = buildExportList(query); -
- // 不需要标题栏和二级标题 这里仅定义sheet的名称
- final ExportParams exportParams = new ExportParams(null, null, "导出数据");
- // 设置导出样式
- exportParams.setStyle(ExcelStyleUtil.class);
- // easypoi的核心方法 将java的List写成excel的每一行数据
- final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);
-
- // 导出文件
- try {
- String fileName = new String("记录导出结果.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
- response.setContentType("application/octet-stream");
- response.setHeader("Content-disposition", "attachment;filename=" + fileName);
- OutputStream outputStream = response.getOutputStream();
- response.flushBuffer();
- sheets.write(outputStream);
- // 写完数据关闭流
- outputStream.close();
- log.warn("export success. [pageQuery={} total={}] ", JSON.toJSONString(pageQuery), exportList.size());
- } catch (IOException e) {
- log.error("export error! [pageQuery={}] e:{}", JSON.toJSONString(pageQuery), e);
- }
- }
- }
5. 接口定义
一般的导出接口controller写法,默认GET方式,比较简单;
- /**
- * 我的可读工单导出
- */
- @RequestMapping(value = {"/audit/flow/export"})
- public DefaultResponseDTO
export(AuditFlowPageQuery pageQuery, HttpServletResponse response) { - try {
- userAuditFlowService.export(response, pageQuery);
- return DefaultResponseDTO.success(Boolean.TRUE);
- } catch (Exception e) {
- log.error("audit flow export error", e);
- return DefaultResponseDTO.fail(ResultCodeEnum.SERVER_BUSYNESS, "请稍后重试");
- }
- }
场景和代码都比较简单,如果想使用更加复杂的excel相关API的用法,可以参考下面的文章;
关于在同一个excel文件中导出多个sheet的方法,在网上搜了一圈,没有写的很清楚的,要么是复制粘贴的帖子,要么是把自己的代码原封不动复制上去,能不能编译通过都另说;自己通过查看easypoi的API,看到有一条这样的方法,如下:
- // cn.afterturn.easypoi.excel.ExcelExportUtil#exportExcel
-
- /**
- * 根据Map创建对应的Excel(一个excel 创建多个sheet)
- *
- * @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data
- * Collection 数据
- * @return
- */
- public static Workbook exportExcel(List {
- Workbook workbook = getWorkbook(type, 0);
- for (Map
map : list) { - ExcelExportService service = new ExcelExportService();
- service.createSheet(workbook, (ExportParams) map.get("title"),
- (Class>) map.get("entity"), (Collection>) map.get("data"));
- }
- return workbook;
- }
注意,这个方法仅支持导出xls类型的excel,也就是第二个参数必须为ExcelType.HSSF,如果改成ExcelType.XSSF,执行时会报错,原因具体不清楚,以验证过;一般来说单sheet行数小于65535,差不多够用了,否则就去操作原生的Apache poi吧;
注意:以上的描述有误,感谢评论区小伙伴的提醒,此方法在使用时,方法参数的第二位ExcelType,需要和导出配置ExportParams对象中指定的类型一致,否则会报错;
示例代码如下:
- @Override
- public void exportMultiSheetWorkbook(List
> exportDataSet, HttpServletResponse response)
{ - // 多个sheet配置参数
- final List
- exportDataSet.forEach(exportList -> {
- final String sheetName = "sheet名称";
- Map
exportMap = Maps.newHashMap(); - final ExportParams exportParams = new ExportParams(null, sheetName, ExcelType.HSSF);
- exportParams.setStyle(ExcelSimpleStyleUtil.class);
- // 以下3个参数为API中写死的参数名 分别是sheet配置/导出类(注解定义)/数据集
- exportMap.put("title", exportParams);
- exportMap.put("entity", MyExport.class);
- exportMap.put("data", exportList);
- // 加入多sheet配置列表
- sheetsList.add(exportMap);
- });
-
- // 导出文件
- try {
- // 核心方法:导出含多个sheet的excel文件 【注意,该方法第二个参数必须与上述的ExportParams对象指定的导出类型一致,默认ExcelType.HSSF格式,否则执行此方法时会报错!!!】
- final Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
-
- String fileName = new String("文件名.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
- response.setContentType("application/octet-stream");
- response.setHeader("Content-disposition", "attachment;filename=" + fileName);
- OutputStream outputStream = response.getOutputStream();
- response.flushBuffer();
- workbook.write(outputStream);
- // 写完数据关闭流
- outputStream.close();
- log.warn("导出成功");
- } catch (IOException e) {
- log.error("导出异常 e:{}", e);
- }
- }
实际工程中,可能会遇到"模板导出"的场景,如固定的表标题、表头,仅需要在固定的格式下面新增列去填充数据;例如:

这时候,可以使用easypoi的另一个API,带TemplateExportParams参数的方法;
其步骤是:
准备excel模板,在指定的单元格填充好easypoi支持的表达式;
再根据模板excel文件生成TemplateExportParams对象;
调用API,传入上面的TemplateExportParams对象和数据对象集合,返回excel对象;
示例代码:
- @Override
- public Workbook export(UnitInfoQuery unitInfoQuery) {
- TemplateExportParams params = new TemplateExportParams("doc/导出模板.xls");
- List
unitInfoDOList = unitInfoDAO.selectByCondition(unitInfoQuery); - List
units = unitInfoDOList.stream().map(beanConvertMapper::convert2ExcelUnitInfoVO).collect(Collectors.toList()); - Map
map = new HashMap<>(); - // 字段名与导出excel模板表达式对应
- map.put("units", units);
- return ExcelExportUtil.exportExcel(params, map);
- }
注意表达式的写法,变量名这些,踩坑经验可参考:EasyPOI模板导出的坑TemplateExportParams;
参考: