百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

分分钟搞定 Excel 数据导出

toyiye 2024-05-09 18:41 29 浏览 0 评论

1. 概览

数据导出是日常开发的常见功能,及将数据导出为Excel并提供下载。Java 生态存在大量的 Excel 操作类库,基于这些类库便可完成相关功能。这样,大量繁杂、无意义的代码耗费着宝贵的人力资源,大家距离 996 又近了几分,那有没有更优解呢?

1.1. 背景

刚刚接到产品需求,准备为众多“列表”功能增加下载支持,简单来说就是:新增一个“导出”按钮,点击时,将列表数据导入到 Excel 并进行下载。

需求很简单,功能也很明确,接到需求后,小伙伴们着手准备:

  1. 使用 poi 可以将数据写入 Excel,但 poi 的 api 过于复杂,存在一定的学习成本;
  2. 工作量巨大,涉及较多的列表页面,每个列表的字段数量也不少;
  3. 期望列表展示和Excel 数据同源,发生变更时只需要修改一次,列表和Excel能同时生效;

不知道你怎么想,面对这些枯燥的体力工作,我极为反感。

1.2. 目标

“懒” 是人类进步的“原动力”。手懒心不懒,让脑力解放体力!!!

设计目标如下:

  1. 屏蔽复杂的 API,降低使用门槛;
  2. 使用声明式替代编码,提升开发效率;
  3. 为一般场景提供通用能力,方便快速支持以下能力:
  • 自定义标题
  • 自定义顺序
  • 支持嵌套对象
  • 支持自定义格式
  • 支持自定义样式
  1. 为特殊场景提供定制能力,以满足个性化需求;

2. 快速入门

2.1. 引入 starter 以及相关依赖

首先,在 pom 文件中增加 excelasbean starter 以及 poi 依赖。

<dependency>
    <groupId>com.geekhalo.lego</groupId>
    <artifactId>lego-starter-excelasbean</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
</dependency>

2.2. 使用 ExcelAsBeanService 完成 excel 导出

其次,将 ExcelAsBeanService 注入到自己的 Sevice 中,使用 write 等一系列方法完成 数据写入。

示例代码如下:

// 注入 excelAsBeanService
// 由 ExcelAsBeanAutoConfiguration 完成 ExcelAsBeanService 的注册
@Autowired
private ExcelAsBeanService excelAsBeanService;
public <D extends User> HSSFWorkbook downloadUser(Class<D> cls, Supplier<D> supplier){
    // 1. 准备数据
    List<D> users = createUser(100, supplier);
    // 2. 创建 Workbook
    HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
    // 3. 向 Sheet 中写入数据
    this.excelAsBeanService.writHeaderAndDataToSheet(hssfWorkbook,"User", cls, users);
    // 4. 返回 workbook
    return hssfWorkbook;
}

ExcelAsBeanService 中涉及写操作包括:

方法名

含义

writHeaderAndDataToSheet

一次性写入 标题 和 数据

writDataToSheet

仅写入数据,主要用于数据追加操作

2.3. 在 JavaBean 上增加相关注解

最后,只要在 JavaBean 增加相关注解,便可以导出 Excel。

从简单到复杂,框架提供了多种定制能力,具体如下:

2.3.1. 简单导出

最简单的方式,只需增加 @HSSFHeader 注解,并指定好 Header 的标题即可。

示例代码如下:

@Data
public class UserV1 implements User{
    @HSSFHeader(title = "编号")
    private Long id;
    @HSSFHeader(title = "姓名")
    private String name;
    @HSSFHeader(title = "生日")
    private Date birthAt;
    @HSSFHeader(title = "年龄")
    private Integer age;
}

运行效果如下:

简单导出

备注:@HSSFHeader 具有标记作用,JavaBean 中没有 @HSSFHeader 标记的字段或方法不会进行导出。

2.3.2. 自定义列顺序

默认情况下,Excel 中的列与 JavaBean 属性定义顺序相关;如有需要,可自定义列的顺序。

增加自定义列顺序,相关代码如下:

@Data
public class UserV2 implements User{
    @HSSFHeader(title = "编号", order = 1)
    private Long id;
    @HSSFHeader(title = "姓名", order = 2)
    private String name;
    @HSSFHeader(title = "生日", order = 4)
    private Date birthAt;
    @HSSFHeader(title = "年龄")
    @HSSFShowOrder(3)
    private Integer age;
}

可以通过 @HSSFHeader 的 order 指定顺序,也可以使用 @HSSFShowOrder 进行指定,具体效果如下:

自定义列顺序

见图,年龄 和 生日 的顺序发生变化。

2.3.3. 导出嵌入对象

当 JavaBean 属性为另一个对象时,我们需要将关联对象也一并导出。

首先,定义关联对象,示例如下:

@Data
@Builder
public class Address {
    private Long id;
    @HSSFHeader(title = "省")
    private String l1;
    @HSSFHeader(title = "市")
    private String l2;
    @HSSFHeader(title = "区")
    private String l3;
    @HSSFHeader(title = "详细地址")
    private String l4;
}

关联对象只是在 Address 上增加了 @HSSFHeader 注解。

其次,使用 @HSSFEmbedded 对嵌入对象进行标记。示例如下:

@Data
public class UserV3 implements User{
    @HSSFHeader(title = "编号", order = 1)
    private Long id;
    @HSSFHeader(title = "姓名", order = 2)
    private String name;
    @HSSFHeader(title = "生日", order = 4)
    private Date birthAt;
    @HSSFHeader(title = "年龄", order = 3)
    private Integer age;
    @HSSFEmbedded
    @HSSFShowOrder(5)
    private Address address;
}

运行效果如下:

导出嵌入对象

如图,Address 内部属性已经完全展开并写入到 Excel。

2.3.4. 自定义格式化方法

当直接对属性进行展示不能满足需求时,可以通过自定义方法对输出内容进行格式化。

比如,需要对 Address 对象中的内容进行自定义展示,示例如下:

@Data
public class UserV4 implements User{
    @HSSFHeader(title = "编号", order = 1)
    private Long id;
    @HSSFHeader(title = "姓名", order = 2)
    private String name;
    @HSSFHeader(title = "生日", order = 4)
    private Date birthAt;
    @HSSFHeader(title = "年龄", order = 3)
    private Integer age;
    @HSSFEmbedded
    @HSSFShowOrder(5)
    private Address address;
    @HSSFHeader(title = "详细地址", order = 6)
    public String showAddress(){
        if (this.address == null){
            return "暂无地址";
        }
        return new StringBuilder()
                .append(this.address.getL1()).append(", ")
                .append(this.address.getL2()).append(", ")
                .append(this.address.getL3()).append(", ")
                .append(this.address.getL4())
                .toString();
    }
}

首先,将展示规则封装到方法中,如 showAddress();然后,在方法上增加 @HSSFHeader 注解。

运行效果如下:

导出嵌入对象

如图,Excel 中新增 “详细地址” 列。

2.3.5. 添加样式

如果觉得展示比较枯燥,可以自定义 Header 和 Data 的显示样式。

在类上指定全局 Header 样式,示例如下:

@Data
@HSSFHeaderStyle("header")
public class UserV5 implements User{
    @HSSFHeader(title = "编号", order = 1)
    private Long id;
    @HSSFHeader(title = "姓名", order = 2)
    private String name;
    @HSSFHeader(title = "生日", order = 4)
    private Date birthAt;
    @HSSFHeader(title = "年龄", order = 3)
    private Integer age;
    @HSSFEmbedded
    @HSSFShowOrder(5)
    private Address address;
    @HSSFHeader(title = "详细地址", order = 6)
    public String showAddress(){
        if (this.address == null){
            return "暂无地址";
        }
        return new StringBuilder()
                .append(this.address.getL1()).append(", ")
                .append(this.address.getL2()).append(", ")
                .append(this.address.getL3()).append(", ")
                .append(this.address.getL4())
                .toString();
    }
}

在 UserV5 类上增加 @HSSFHeaderStyle("header"),指定表头使用 header 样式。

运行效果如下:

添加样式

如图,表头的样式发生了变化。

备注:自定义样式,需要提供 HSSFCellStyleFactory 实现,详见 “设计&扩展” 部分

3. 设计&扩展

3.1. 核心设计

3.1.1. 整体设计

整体设计如下:

整体设计

按照层级结构,将核心组件拆分为:

  1. 每一个 class 通过解析后,获得一个与之对应的 HSSFSheetWriter;
  2. 每一个 HSSFSheetWriter 包含一个 HSSFRowWriter,完成“写标题”和“写数据”操作;
  3. 每一个 HSSFRowWriter 包含若干个 HSSFColumnWriter 对象,共同完成每一行的写;
  4. 每一个 HSSFColumnWriter 包含两个 HSSFCellWriterChain,负责写标题 和 数据;
  5. 每一个 HSSFCellWriterChain 包含 HSSFValueSupplier、HSSFCellConfigurator、HSSFCellWriter 共同完成一个 Cell 的写操作;

其中,操作的核心逻辑基本在 HSSFCellWriterChain 中,其他结构主要完成组件组装。

3.1.1. CellWriterChain 设计

CellWriterChain 是写入流程的核心,整体设计如下:

写入链设计

从写流程来看,包括:

  1. HSSFValueSupplier 从 JavaBean 中获取数据,包括从注解中获取标题信息,从字段读取数据,执行方法获取数据等;
  2. HSSFCellConfigurator 对 Cell 进行配置,比如设置自适应宽度、设置显示样式等;
  3. HSSFCellWriter 将数据写入到 Cell 中,包括简单数据 String、Long、Double 等,也包括自定义 Date 格式等;

从装配流程来看,包括:

  1. Spring 容器中注册有大量的 Factory,包括HSSFHeaderValueSupplierFactory、HSSFDataValueSupplierFactory、HSSFHeaderCellConfiguratorFactory、HSSFDataCellConfiguratorFactory、HSSFHeaderCellWriterFactory、HSSFDataCellWriterFactory等
  2. 将众 Factory 注入到对于的 Factories,由Factories 对外提供统一入口。Factories 包括 HSSFValueSupplierFactories、HSSFCellConfiguratorFactories、HSSFCellWriterFactories 等;
  3. DefaultHSSFCellWriterChainFactory 从 Factories 中获取组件,将其封装为 HSSFCellWriterChain;
  4. 最后,由 HSSFCellWriterChain 完成数据写入流程;

3.2. 功能扩展

为了应对个性化需求,框架提供了众多扩展点,其中最重要的扩展点包括,HSSFValueSupplier、HSSFCellWriter 和 HSSFCellConfigurator。

三者协助组成 HSSFCellWriterChain,完成数据写入操作。

3.2.1. HSSFValueSupplier

从 Bean 中提取数据。

HSSFValueSupplier 定义如下:

@FunctionalInterface
public interface HSSFValueSupplier<T, R> extends Function<T, R> {
}

直接继承自 Function,输入为 JavaBean 对象,输出为待写入数据。

目前,系统实现如下:

系统实现

功能

FixValueSupplier

提供固定值作为写入数据,比如在 HSSFHeader 配置的 title 信息

FieldBasedDataValueSupplier

从 field 中读取数据

MethodBasedValueSupplier

执行 method 获取数据

备注:一般情况下无需进行扩展

3.2.2. HSSFCellConfigurator

在执行写入操作前,对 Cell 进行配置。

HSSFCellConfigurator 定义如下:

public interface HSSFCellConfigurator {
    /**
     * 对 Cell 进行配置
     * @param context
     * @param columnIndex
     * @param cell
     */
    void configFor(HSSFCellWriterContext context, int columnIndex, HSSFCell cell);
}

目前,系统中实现包括:

系统实现

功能

AutoSizeCellConfigurator

为列设置自适应大小

HSSFCellStyleConfigurator

为列设置显示样式

如需对样式进行定制,需实现 HSSFCellStyleFactory 并根据 name 返回不同的样式。实例代码如下:

@Component
public class DemoHSSFCellStyleFactory implements HSSFCellStyleFactory {
    @Override
    public HSSFCellStyle createStyle(HSSFCellWriterContext context, String name) {
        if ("header".equalsIgnoreCase(name)){
            return createForHeader(context.getWorkbook());
        }
        if ("data".equalsIgnoreCase(name)){
            return createForData(context.getWorkbook());
        }
        return null;
    }
    private HSSFCellStyle createForData(HSSFWorkbook workbook) {
        // 定义 data 展示样式
        return style;
    }
    private HSSFCellStyle createForHeader(HSSFWorkbook workbook) {
        // 定义 header 展示样式
        return style;
    }
}

3.2.3. HSSFCellWriter

负责向 Cell 中写入数据,比如对数据进行格式化。

HSSFCellWriter 接口定义如下:

public interface HSSFCellWriter<D> {
    /**
     * 向 Cell 写入数据
     * @param context
     * @param cell 待写入的 Cell
     * @param data 待写入的 data
     */
    void write(HSSFCellWriterContext context, HSSFCell cell, D data);
}

目前,系统中的实现包括:

实现类

功能

DefaultHSSFCellWriter

默认实现,主要用于处理简单属性

DateFormatHSSFCellWriter

对 Date、LocalDate、LocalDateTime 等进行自定义格式化

备注:如需复杂的转换,可以通过扩展 HSSFCellWriter 的方式实现。

4. 项目信息

项目仓库地址:https://gitee.com/litao851025/lego

项目文档地址:https://gitee.com/litao851025/lego/wikis/support/ExcelAsBean-%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%87%BA

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码