新一代 Excel 导出工具:ExcelUtil + RunnerUtil 介绍

背景 —— 提一个好的问题

开发过程中经常会遇到 Excel 导出的情况,尤其是在企业开发中,涉及到客户信息、财务报表、市场分析等,情景非常多。平常开发过程中大多都会针对每个导出单独写一套代码,随着导出越来越多,心里便想:有没有一个足够通用东西可以让我们不用写这么多代码来实现 Excel 导出?

带着这个问题便开始了自己的“ExcelUtil”之路,在这过程中主要接触过 easypoi,但还是不太满足。因为 easypoi 和大多数 Java 库一样:基于字段写配置。当然不是说这个不好,有很多库都这样,比如 fastjson、Jackson 等都是在字段上写注解,描述这个字段有些什么信息或作用等。但对于 Excel 导出,我总觉得还有更加通用的方式。

经过一段时间的摸索和发掘,在前端的 table 标签上找到了灵感,认为这个方式很好、非常好。table 标签本身包含了很多描述信息,像行、列、合并行、合并列这些与 excel 的 sheet 页“惊人的相似”,再加上近几年前端三大框架的大力发展,尤其是 angular 和 vue 这两个框架在标签上自定义属性的方式进一步让我在写 ExcelUtil 过程中得到了不少启发。

简介

ExcelUtil 和 RunnerUtilGitHub) 一样,大概是在今年 5 到 6 月写的,最近又重新整理了一下,已上传 GitHub # ExcelUtil

ExcelUtil 根据 excel 文件、sheet 页、row 行、cell 单元格这样的层次结构分别定义了自己的作用域,每个作用域内可以一定程度上自定义变量等,作用域之间互不影响,同名变量下层作用域等声明优先于上层作用域等这些与 java、JavaScript 等语言的作用域结构一致。

使用介绍

  1. 使用 ExcelUtil 的之前首先要准备的就是数据,数据并没有特殊的格式要求,可以是任意 Java 类型数据,如 Collection、Iterable、Iterator(迭代器模式可,这是在一次面试时得到的启发,可用于超大 Excel 导出,虽然后来没通过,但仍然很感谢那位面试官!)、Map、数组、POJO、Number等。

  2. 第二步是生成 Workbook 位置的方法上进行“注解编程” —— 对的,Java 的注解功能很强大,可以在 Java 内部又单独作为 Java 内的“编程语言”(其实就是写了个简单的解析器而言,捂脸一笑)。

// 在什么地方导出,就在那个方法上进行声明式“注解编程”
// 首先要声明这是一个 Excel,用 type 指定是 xls 或者 xlsx
@TableExcel(type = TableExcel.Type.XLS, value = {
    /* 
     * value 包含的是左右 sheet 页的信息
     * 自 sheet 向下,每个标签可以判断、循环等
     * 用 sheetName 指定 sheet 名
     * 为什么要用单引号再多包裹一层呢?详见 RunnerUtil
     * 因为这里面的所有内容都是用 RunnerUtil 解析的,需要符合它的格式
     */ 
    @TableSheet(sheetName = "'人员信息'", value = {
        /*
         * 在这儿声明了一个名为 names 的数组,用作标题
         */
        @TableRow(var = "names = {'序号','姓名','性别','年龄','电话','家庭住址', '备注'}", value = {
            /*
             * 这儿用了迭代,迭代 row 上声明的 names
             * 这个迭代将按 names 的内容生成对应数量和内容的 cell 单元格
             */
            @TableCell(var = "name:names", value = name)
        }),
        /*
         * 上面 cell 的迭代用的是冒号,这儿用了 in,二者意义完全一样
         * 支持 in 完全是为了向灵感的来源(前端)致敬
         * 但是 in 并不是关键字,仍可作为普通变量
         * 不同的是 in 的两端至少各有一个空格
         * 可迭代的数据类型一会儿详细介绍
         */
        @TableRow(var = "($rowData, index) in collect", value = {
            @TableCell("index + 1"), // 序号
            @TableCell("$rowData.name"), // 姓名
            @TableCell("$rowData.sex"), // 性别
            @TableCell("$rowData.age"), // 年龄
            @TableCell("$rowData.mobile"), // 电话
            @TableCell("$rowData.address"), // 家庭住址
            // 最后这个对于上面的备注,这儿有个 when,只有 index == 0 才创建这个单元格
            // 同时这儿还用到了并合并行,另外 colspan 是合并列
            @TableCell(when = "index == 0", rowspan = "data.size()")
        })
    })
})
public Workbook exportExcel(Object data){
    /*
     * 写好注解后只需要调用这个方法便可得到一个 Workbook
     */
    return ExcelUtil.render(data);
}
复制代码
  • ExcelUtil.render(data); 在渲染中 in (或冒号 :)可迭代的数据有:
  1. number(整数),如 var = "$item in 10",循环十次;
  2. 字符串,迭代出字符串中的每个字符,但由于 RunnerUtil 是不支持 char 类型数据的,所以实际上迭代出来的是单个字符的字符串
  3. Collection、Iterable、List、Set 等集合。
  4. Map,迭代出来的是每一个键值对的值;
  5. POJO,普通 Java 对象按字段名迭代
  • when 后面的表达式返回值必须是 boolean 类型
  • colspan、rowspan 表达式返回值必须是 int 类型
  • 其他的还有 heigit、width 等也必须是 int 类型

使用效果:

https://user-gold-cdn.xitu.io/2018/11/28/16758316e9699b4d?w=1457&h=813&f=png&s=254543

  • 生成的对应 Excel 效果图

https://user-gold-cdn.xitu.io/2018/11/28/16758316e9782ec1?w=649&h=309&f=png&s=12588

性能测试

贴一个本工具导出的 10 列 Excel 的性能测试表(本机环境 i7-8700K 16G Win10)

行数(万行) 生成数据耗时(ms) write到文件耗时(ms) 总耗时(ms)
100 6,182 5,565 11,747
300 14,800 16,693 31,493
500 25,876 27,317 53,193
700 36,121 42,171 78,292
999 53,532 54,745 108,277
4000 240,453 271,832 512,285
6000 366,987 423,351 790,338
8000 528,654 498,490 1,027,144

从这个数据可以看出,随着数据量增加,时间与数据的关系呈正相关性,比较接近线性关系,100 万行数据生成 Workbook 耗时 6s,总耗时 12s,在正常业务场景下能满足时间的要求。

其他说明

  • 当 Excel 数据量超过 150 万行时,不建议用 xls 格式(这个数据在不同机器上应该有差异,本机 150 万行的 xls 能正常导出,180 万行就 OOM 了);
  • 当数据量超过 500 万行时,TableExcel 的 type 值应为 SUPER(type = TableExcel.Type.SUPER),SUPER 对应的也是 xlsx 格式,但是 SUPER 是用来支持超大数据导出的;
  • 150 万行和 500 万行基本是极限值了。

ExcelUtil # GitHub

RunnerUtil 的用法介绍

猜你喜欢

转载自juejin.im/post/5bfdf1aa6fb9a049a62c460f