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 依賴。
2。2。 使用 ExcelAsBeanService 完成 excel 匯出
其次,將 ExcelAsBeanService 注入到自己的 Sevice 中,使用 write 等一系列方法完成 資料寫入。
示例程式碼如下:
// 注入 excelAsBeanService// 由 ExcelAsBeanAutoConfiguration 完成 ExcelAsBeanService 的註冊@Autowiredprivate ExcelAsBeanService excelAsBeanService;public
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;}
執行效果如下:
簡單匯出
備註:@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 進行指定,具體效果如下:
自定義列順序
見圖,年齡 和 生日 的順序發生變化。
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;}
執行效果如下:
匯出嵌入物件
如圖,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 中新增 “詳細地址” 列。
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。 整體設計
整體設計如下:
整體設計
按照層級結構,將核心元件拆分為:
每一個 class 透過解析後,獲得一個與之對應的 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 格式等;
從裝配流程來看,包括:
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
直接繼承自 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
目前,系統中的實現包括:
實現類
功能
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