分分鐘搞定 Excel 資料匯出

1。 概覽

資料匯出是日常開發的常見功能,及將資料匯出為Excel並提供下載。Java 生態存在大量的 Excel 操作類庫,基於這些類庫便可完成相關功能。這樣,大量繁雜、無意義的程式碼耗費著寶貴的人力資源,大家距離 996 又近了幾分,那有沒有更優解呢?

1。1。 背景

剛剛接到產品需求,準備為眾多“列表”功能增加下載支援,簡單來說就是:新增一個“匯出”按鈕,點選時,將列表資料匯入到 Excel 並進行下載。

需求很簡單,功能也很明確,接到需求後,小夥伴們著手準備:

使用 poi 可以將資料寫入 Excel,但 poi 的 api 過於複雜,存在一定的學習成本;

工作量巨大,涉及較多的列表頁面,每個列表的欄位數量也不少;

期望列表展示和Excel 資料同源,發生變更時只需要修改一次,列表和Excel能同時生效;

不知道你怎麼想,面對這些枯燥的體力工作,我極為反感。

1。2。 目標

“懶” 是人類進步的“原動力”。手懶心不懶,讓腦力解放體力!!!

設計目標如下:

遮蔽複雜的 API,降低使用門檻;

使用宣告式替代編碼,提升開發效率;

為一般場景提供通用能力,方便快速支援以下能力:

自定義標題

自定義順序

支援巢狀物件

支援自定義格式

支援自定義樣式

為特殊場景提供定製能力,以滿足個性化需求;

2。 快速入門

2。1。 引入 starter 以及相關依賴

首先,在 pom 檔案中增加 excelasbean starter 以及 poi 依賴。

    com。geekhalo。lego    lego-starter-excelasbean    0。0。1-SNAPSHOT    org。apache。poi    poi

2。2。 使用 ExcelAsBeanService 完成 excel 匯出

其次,將 ExcelAsBeanService 注入到自己的 Sevice 中,使用 write 等一系列方法完成 資料寫入。

示例程式碼如下:

// 注入 excelAsBeanService// 由 ExcelAsBeanAutoConfiguration 完成 ExcelAsBeanService 的註冊@Autowiredprivate ExcelAsBeanService excelAsBeanService;public  HSSFWorkbook downloadUser(Class cls, Supplier supplier){    // 1。 準備資料    List 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 的標題即可。

示例程式碼如下:

@Datapublic class UserV1 implements User{    @HSSFHeader(title = “編號”)    private Long id;    @HSSFHeader(title = “姓名”)    private String name;    @HSSFHeader(title = “生日”)    private Date birthAt;    @HSSFHeader(title = “年齡”)    private Integer age;}

執行效果如下:

分分鐘搞定 Excel 資料匯出

簡單匯出

備註:@HSSFHeader 具有標記作用,JavaBean 中沒有 @HSSFHeader 標記的欄位或方法不會進行匯出。

2。3。2。 自定義列順序

預設情況下,Excel 中的列與 JavaBean 屬性定義順序相關;如有需要,可自定義列的順序。

增加自定義列順序,相關程式碼如下:

@Datapublic 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 進行指定,具體效果如下:

分分鐘搞定 Excel 資料匯出

自定義列順序

見圖,年齡 和 生日 的順序發生變化。

2。3。3。 匯出嵌入物件

當 JavaBean 屬性為另一個物件時,我們需要將關聯物件也一併匯出。

首先,定義關聯物件,示例如下:

@Data@Builderpublic 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 對嵌入物件進行標記。示例如下:

@Datapublic 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;}

執行效果如下:

分分鐘搞定 Excel 資料匯出

匯出嵌入物件

如圖,Address 內部屬性已經完全展開並寫入到 Excel。

2。3。4。 自定義格式化方法

當直接對屬性進行展示不能滿足需求時,可以透過自定義方法對輸出內容進行格式化。

比如,需要對 Address 物件中的內容進行自定義展示,示例如下:

@Datapublic 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 資料匯出

匯出嵌入物件

如圖,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 樣式。

執行效果如下:

分分鐘搞定 Excel 資料匯出

新增樣式

如圖,表頭的樣式發生了變化。

備註:自定義樣式,需要提供 HSSFCellStyleFactory 實現,詳見 “設計&擴充套件” 部分

3。 設計&擴充套件

3。1。 核心設計

3。1。1。 整體設計

整體設計如下:

分分鐘搞定 Excel 資料匯出

整體設計

按照層級結構,將核心元件拆分為:

每一個 class 透過解析後,獲得一個與之對應的 HSSFSheetWriter;

每一個 HSSFSheetWriter 包含一個 HSSFRowWriter,完成“寫標題”和“寫資料”操作;

每一個 HSSFRowWriter 包含若干個 HSSFColumnWriter 物件,共同完成每一行的寫;

每一個 HSSFColumnWriter 包含兩個 HSSFCellWriterChain,負責寫標題 和 資料;

每一個 HSSFCellWriterChain 包含 HSSFValueSupplier、HSSFCellConfigurator、HSSFCellWriter 共同完成一個 Cell 的寫操作;

其中,操作的核心邏輯基本在 HSSFCellWriterChain 中,其他結構主要完成元件組裝。

3。1。1。 CellWriterChain 設計

CellWriterChain 是寫入流程的核心,整體設計如下:

分分鐘搞定 Excel 資料匯出

寫入鏈設計

從寫流程來看,包括:

HSSFValueSupplier 從 JavaBean 中獲取資料,包括從註解中獲取標題資訊,從欄位讀取資料,執行方法獲取資料等;

HSSFCellConfigurator 對 Cell 進行配置,比如設定自適應寬度、設定顯示樣式等;

HSSFCellWriter 將資料寫入到 Cell 中,包括簡單資料 String、Long、Double 等,也包括自定義 Date 格式等;

從裝配流程來看,包括:

Spring 容器中註冊有大量的 Factory,包括HSSFHeaderValueSupplierFactory、HSSFDataValueSupplierFactory、HSSFHeaderCellConfiguratorFactory、HSSFDataCellConfiguratorFactory、HSSFHeaderCellWriterFactory、HSSFDataCellWriterFactory等

將眾 Factory 注入到對於的 Factories,由Factories 對外提供統一入口。Factories 包括 HSSFValueSupplierFactories、HSSFCellConfiguratorFactories、HSSFCellWriterFactories 等;

DefaultHSSFCellWriterChainFactory 從 Factories 中獲取元件,將其封裝為 HSSFCellWriterChain;

最後,由 HSSFCellWriterChain 完成資料寫入流程;

3。2。 功能擴充套件

為了應對個性化需求,框架提供了眾多擴充套件點,其中最重要的擴充套件點包括,HSSFValueSupplier、HSSFCellWriter 和 HSSFCellConfigurator。

三者協助組成 HSSFCellWriterChain,完成資料寫入操作。

3。2。1。 HSSFValueSupplier

從 Bean 中提取資料。

HSSFValueSupplier 定義如下:

@FunctionalInterfacepublic interface HSSFValueSupplier extends Function {}

直接繼承自 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 返回不同的樣式。例項程式碼如下:

@Componentpublic 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 {    /**     * 向 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