数据导出是日常开发的常见功能,及将数据导出为Excel并提供下载。 Java 生态存在大量的 Excel 操作类库,基于这些类库便可完成相关功能。这样,大量繁杂、无意义的代码耗费着宝贵的人力资源,大家距离 996 又近了几分,那有没有更优解呢?
刚刚接到产品需求,准备为众多“列表”功能增加下载支持,简单来说就是:新增一个“导出”按钮,点击时,将列表数据导入到 Excel 并进行下载。
需求很简单,功能也很明确,接到需求后,小伙伴们着手准备:
不知道你怎么想,面对这些枯燥的体力工作,我极为反感。
“懒” 是人类进步的“原动力”。手懒心不懒,让脑力解放体力!!!
设计目标如下:
首先,在 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>
其次,将 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 | 仅写入数据,主要用于数据追加操作 |
最后,只要在 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 中新增 “详细地址” 列。
如果觉得展示比较枯燥,可以自定义 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.1.1. 整体设计
整体设计如下:
按照层级结构,将核心组件拆分为:
HSSFSheetWriter
;HSSFSheetWriter
包含一个 HSSFRowWriter
,完成“写标题”和“写数据”操作;HSSFRowWriter
包含若干个 HSSFColumnWriter
对象,共同完成每一行的写;HSSFColumnWriter
包含两个 HSSFCellWriterChain
,负责写标题 和 数据;HSSFCellWriterChain
包含 HSSFValueSupplier
、HSSFCellConfigurator
、HSSFCellWriter
共同完成一个 Cell 的写操作;其中,操作的核心逻辑基本在 HSSFCellWriterChain
中,其他结构主要完成组件组装。
3.1.1. CellWriterChain 设计
CellWriterChain
是写入流程的核心,整体设计如下:
从写流程来看,包括:
HSSFValueSupplier
从 JavaBean 中获取数据,包括从注解中获取标题信息,从字段读取数据,执行方法获取数据等;HSSFCellConfigurator
对 Cell 进行配置,比如设置自适应宽度、设置显示样式等;HSSFCellWriter
将数据写入到 Cell 中,包括简单数据 String、Long、Double 等,也包括自定义 Date 格式等;从装配流程来看,包括:
HSSFHeaderValueSupplierFactory
、HSSFDataValueSupplierFactory
、HSSFHeaderCellConfiguratorFactory
、HSSFDataCellConfiguratorFactory
、HSSFHeaderCellWriterFactory
、HSSFDataCellWriterFactory
等HSSFValueSupplierFactories
、HSSFCellConfiguratorFactories
、HSSFCellWriterFactories
等;DefaultHSSFCellWriterChainFactory
从 Factories 中获取组件,将其封装为 HSSFCellWriterChain
;HSSFCellWriterChain
完成数据写入流程;为了应对个性化需求,框架提供了众多扩展点,其中最重要的扩展点包括,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
的方式实现。
网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。
加入交流群
请使用微信扫一扫!