欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java利用構(gòu)建器模式重構(gòu)Excel導(dǎo)出工具類

 更新時(shí)間:2025年06月04日 08:59:43   作者:DebugYourCareer  
在Java企業(yè)級開發(fā)中,Excel導(dǎo)出功能幾乎成為業(yè)務(wù)系統(tǒng)的標(biāo)準(zhǔn)配置,本文將介紹如何通過構(gòu)建器模式和流暢接口設(shè)計(jì),重構(gòu)Excel導(dǎo)出工具類,實(shí)現(xiàn)API的優(yōu)雅封裝與調(diào)用,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

引言:Excel導(dǎo)出的痛點(diǎn)與重構(gòu)價(jià)值

在Java企業(yè)級開發(fā)中,Excel導(dǎo)出功能幾乎成為業(yè)務(wù)系統(tǒng)的標(biāo)準(zhǔn)配置。從數(shù)據(jù)報(bào)表到業(yè)務(wù)分析,從用戶信息導(dǎo)出到財(cái)務(wù)數(shù)據(jù)歸檔,Excel作為數(shù)據(jù)交換的標(biāo)準(zhǔn)格式,在業(yè)務(wù)場景中扮演著不可或缺的角色。然而,在實(shí)際開發(fā)過程中,我們常常面臨諸多挑戰(zhàn):

  • 參數(shù)爆炸:傳統(tǒng)方法需要傳遞大量參數(shù)(文件名、表頭、數(shù)據(jù)映射等),導(dǎo)致方法簽名冗長
  • 代碼可讀性差:調(diào)用時(shí)參數(shù)順序依賴性強(qiáng),難以直觀理解每個(gè)參數(shù)含義
  • 擴(kuò)展性受限:新增導(dǎo)出配置需修改方法簽名,破壞現(xiàn)有調(diào)用
  • 樣式耦合:業(yè)務(wù)代碼與樣式配置混雜,維護(hù)困難
  • 使用不一致:文件導(dǎo)出與Web導(dǎo)出API不統(tǒng)一,增加學(xué)習(xí)成本

本文將介紹如何通過構(gòu)建器模式流暢接口設(shè)計(jì),重構(gòu)Excel導(dǎo)出工具類,實(shí)現(xiàn)API的優(yōu)雅封裝與調(diào)用。

重構(gòu)方案:構(gòu)建器模式的精妙運(yùn)用

核心設(shè)計(jì)思想

我們采用構(gòu)建器模式對導(dǎo)出參數(shù)進(jìn)行封裝,結(jié)合流暢接口設(shè)計(jì)實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。這種設(shè)計(jì)帶來三大核心優(yōu)勢:

  • 參數(shù)封裝:將所有配置項(xiàng)封裝在內(nèi)部Context對象中
  • 鏈?zhǔn)秸{(diào)用:通過方法鏈實(shí)現(xiàn)自描述式API
  • 默認(rèn)配置:為常用參數(shù)提供合理默認(rèn)值,簡化調(diào)用

完整工具類實(shí)現(xiàn)

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

/**
 * 基于構(gòu)建器模式的Excel導(dǎo)出工具類
 * 提供流暢API接口,支持Web導(dǎo)出和文件導(dǎo)出兩種方式
 */
public class ExcelExporter {
    
    // 參數(shù)封裝內(nèi)部類 - 隱藏實(shí)現(xiàn)細(xì)節(jié)
    private static class ExportContext<T> {
        Object target;                   // 導(dǎo)出目標(biāo)(文件或輸出流)
        String sheetName = "Sheet1";     // 默認(rèn)工作表名稱
        String mainTitle = "數(shù)據(jù)報(bào)表";    // 默認(rèn)主標(biāo)題
        List<String> headers;            // 列標(biāo)題
        List<T> dataList;                // 數(shù)據(jù)列表
        Function<T, List<Object>> dataMapper; // 數(shù)據(jù)映射函數(shù)
        HorizontalCellStyleStrategy styleStrategy; // 樣式策略
        boolean autoDateSuffix = true;   // 是否自動添加日期后綴
        int batchSize = 5000;            // 分批寫入批次大小
    }
    
    /**
     * 創(chuàng)建構(gòu)建器實(shí)例 - 泛型方法確保類型安全
     * @param clazz 數(shù)據(jù)類型(用于類型推斷)
     * @return 構(gòu)建器對象
     */
    public static <T> Builder<T> builder(Class<T> clazz) {
        return new Builder<>();
    }
    
    /**
     * 構(gòu)建器類 - 實(shí)現(xiàn)流暢API
     */
    public static class Builder<T> {
        private final ExportContext<T> context = new ExportContext<>();
        
        /**
         * 設(shè)置導(dǎo)出目標(biāo)為Web響應(yīng)
         * @param response HttpServletResponse對象
         * @param baseFileName 基礎(chǔ)文件名(不含擴(kuò)展名)
         * @return 當(dāng)前構(gòu)建器
         */
        public Builder<T> toWeb(HttpServletResponse response, String baseFileName) {
            try {
                String fileName = buildFileName(baseFileName);
                setResponseHeaders(response, fileName);
                context.target = response.getOutputStream();
            } catch (IOException e) {
                throw new RuntimeException("創(chuàng)建輸出流失敗", e);
            }
            return this;
        }
        
        /**
         * 設(shè)置導(dǎo)出目標(biāo)為文件系統(tǒng)
         * @param filePath 完整文件路徑(含文件名)
         * @return 當(dāng)前構(gòu)建器
         */
        public Builder<T> toFile(String filePath) {
            return toFile(null, filePath);
        }
        
        /**
         * 設(shè)置導(dǎo)出目標(biāo)為文件系統(tǒng)(指定目錄和基礎(chǔ)文件名)
         * @param directory 目錄路徑
         * @param baseFileName 基礎(chǔ)文件名
         * @return 當(dāng)前構(gòu)建器
         */
        public Builder<T> toFile(String directory, String baseFileName) {
            String fileName = buildFileName(baseFileName);
            String fullPath = (directory != null) ? 
                    directory + File.separator + fileName + ".xlsx" : 
                    fileName + ".xlsx";
            
            ensureDirectoryExists(fullPath);
            context.target = new File(fullPath);
            return this;
        }
        
        // 確保目錄存在
        private void ensureDirectoryExists(String fullPath) {
            File file = new File(fullPath);
            File parentDir = file.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                parentDir.mkdirs();
            }
        }
        
        // 其他配置方法(sheetName, mainTitle, headers等)...
        // 完整代碼參考前文實(shí)現(xiàn)
        
        /**
         * 執(zhí)行導(dǎo)出操作 - 核心入口
         */
        public void execute() {
            validateContext();
            applyDefaultStyleIfNeeded();
            doExport(context);
        }
        
        // 參數(shù)校驗(yàn)確保健壯性
        private void validateContext() {
            if (context.target == null) {
                throw new IllegalStateException("導(dǎo)出目標(biāo)未設(shè)置");
            }
            if (context.headers == null || context.headers.isEmpty()) {
                throw new IllegalStateException("列標(biāo)題未設(shè)置");
            }
            if (context.dataMapper == null) {
                throw new IllegalStateException("數(shù)據(jù)映射函數(shù)未設(shè)置");
            }
        }
        
        // 應(yīng)用默認(rèn)樣式
        private void applyDefaultStyleIfNeeded() {
            if (context.styleStrategy == null) {
                context.styleStrategy = createDefaultStyleStrategy();
            }
        }
    }
    
    // 獲取當(dāng)前年月字符串(yyyyMM格式)
    private static String getCurrentYearMonth() {
        return YearMonth.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
    }
    
    // 設(shè)置Web響應(yīng)頭
    private static void setResponseHeaders(HttpServletResponse response, 
                                          String fileName) throws IOException {
        String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-disposition", 
                "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
    }
    
    // 創(chuàng)建默認(rèn)樣式策略 - 專業(yè)美觀的默認(rèn)樣式
    private static HorizontalCellStyleStrategy createDefaultStyleStrategy() {
        // 主標(biāo)題樣式 - 天藍(lán)色背景,16號加粗字體,居中顯示
        WriteCellStyle titleStyle = new WriteCellStyle();
        titleStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
        titleStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont titleFont = new WriteFont();
        titleFont.setFontHeightInPoints((short) 16);
        titleFont.setBold(true);
        titleStyle.setWriteFont(titleFont);

        // 表頭樣式 - 淺黃色背景,12號加粗字體,居中顯示
        WriteCellStyle headerStyle = new WriteCellStyle();
        headerStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex());
        headerStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont headerFont = new WriteFont();
        headerFont.setFontHeightInPoints((short) 12);
        headerFont.setBold(true);
        headerStyle.setWriteFont(headerFont);

        // 內(nèi)容樣式 - 左對齊,自動換行
        WriteCellStyle contentStyle = new WriteCellStyle();
        contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        contentStyle.setWrapped(true); // 自動換行

        return new HorizontalCellStyleStrategy(titleStyle, headerStyle, contentStyle);
    }
    
    // 執(zhí)行導(dǎo)出核心邏輯
    private static <T> void doExport(ExportContext<T> context) {
        List<List<String>> headList = prepareHeaders(context.mainTitle, context.headers);
        List<List<Object>> data = prepareData(context.dataList, context.dataMapper);

        try (ExcelWriter excelWriter = createExcelWriter(context)) {
            WriteSheet writeSheet = EasyExcel.writerSheet(context.sheetName).build();
            writeDataInBatches(excelWriter, writeSheet, data, context.batchSize);
        }
    }
    
    // 創(chuàng)建ExcelWriter實(shí)例
    private static <T> ExcelWriter createExcelWriter(ExportContext<T> context) {
        return EasyExcel.write(context.target)
                .registerWriteHandler(context.styleStrategy)
                .head(prepareHeaders(context.mainTitle, context.headers))
                .build();
    }
    
    // 分批寫入數(shù)據(jù) - 優(yōu)化內(nèi)存使用
    private static void writeDataInBatches(ExcelWriter excelWriter, 
                                          WriteSheet writeSheet,
                                          List<List<Object>> data,
                                          int batchSize) {
        int total = data.size();
        int pages = (int) Math.ceil((double) total / batchSize);

        for (int i = 0; i < pages; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min((i + 1) * batchSize, total);
            excelWriter.write(data.subList(fromIndex, toIndex), writeSheet);
        }
    }
    
    // 準(zhǔn)備表頭數(shù)據(jù)
    private static List<List<String>> prepareHeaders(String mainTitle, List<String> headers) {
        List<List<String>> headList = new ArrayList<>();
        headList.add(Collections.singletonList(mainTitle)); // 主標(biāo)題(跨所有列)
        headList.add(new ArrayList<>(headers)); // 列標(biāo)題
        return headList;
    }
    
    // 準(zhǔn)備表格數(shù)據(jù)
    private static <T> List<List<Object>> prepareData(
            List<T> dataList,
            Function<T, List<Object>> dataMapper) {
        
        if (dataList == null || dataList.isEmpty()) {
            return Collections.emptyList();
        }

        List<List<Object>> result = new ArrayList<>(dataList.size());
        for (T item : dataList) {
            result.add(dataMapper.apply(item));
        }
        return result;
    }
}

設(shè)計(jì)亮點(diǎn)深度解析

1. 流暢API設(shè)計(jì) - 優(yōu)雅的鏈?zhǔn)秸{(diào)用

重構(gòu)后的API采用自然語言式的鏈?zhǔn)秸{(diào)用,代碼即文檔:

// 清晰的導(dǎo)出流程:目標(biāo)→配置→數(shù)據(jù)→執(zhí)行
ExcelExporter.builder(User.class)
    .toWeb(response, "用戶報(bào)表")     // 設(shè)置導(dǎo)出目標(biāo)
    .sheetName("用戶數(shù)據(jù)")          // 配置工作表名
    .mainTitle("2024年用戶分析報(bào)告") // 設(shè)置主標(biāo)題
    .headers("ID", "姓名", "郵箱")  // 設(shè)置列標(biāo)題
    .data(users, user -> Arrays.asList( // 提供數(shù)據(jù)和映射
        user.getId(),
        user.getName(),
        user.getEmail()
    ))
    .batchSize(2000)              // 優(yōu)化大數(shù)據(jù)量處理
    .execute();                   // 執(zhí)行導(dǎo)出

這種設(shè)計(jì)使代碼具有自解釋性,即使不查文檔也能理解每個(gè)步驟的意圖。

2. 智能默認(rèn)值 - 開箱即用的體驗(yàn)

工具類為常用參數(shù)提供精心設(shè)計(jì)的默認(rèn)值:

  • 工作表名稱:默認(rèn)為"Sheet1"
  • 主標(biāo)題:默認(rèn)為"數(shù)據(jù)報(bào)表"
  • 樣式策略:專業(yè)美觀的藍(lán)黃配色方案
  • 日期后綴:自動添加年月標(biāo)識(如"Report_202405")
  • 批處理大小:默認(rèn)5000行/批

這些默認(rèn)值經(jīng)過實(shí)踐檢驗(yàn),能滿足大部分業(yè)務(wù)場景需求,真正實(shí)現(xiàn)開箱即用。

3. 多目標(biāo)統(tǒng)一接口 - 一致的調(diào)用體驗(yàn)

工具類抽象了導(dǎo)出目標(biāo),提供統(tǒng)一的API:

// Web導(dǎo)出 - 直接輸出到HttpServletResponse
.toWeb(response, "用戶報(bào)表")

// 文件導(dǎo)出到指定目錄
.toFile("/reports", "月度銷售")

// 文件導(dǎo)出到當(dāng)前目錄
.toFile("臨時(shí)報(bào)告")

這種設(shè)計(jì)消除了不同導(dǎo)出方式的學(xué)習(xí)成本,開發(fā)者只需關(guān)注業(yè)務(wù)邏輯。

4. 健壯性保障 - 防御式編程

工具類內(nèi)置多重保障機(jī)制:

private void validateContext() {
    // 必須參數(shù)校驗(yàn)
    if (context.target == null) throw ...;
    if (context.headers == null) throw ...;
    if (context.dataMapper == null) throw ...;
    
    // 數(shù)據(jù)空值保護(hù)
    if (dataList == null) return Collections.emptyList();
}

這些校驗(yàn)在execute()方法入口處進(jìn)行,確保導(dǎo)出過程不會因參數(shù)缺失而意外終止。

使用場景示例

場景1:基礎(chǔ)用戶數(shù)據(jù)導(dǎo)出(Web)

@GetMapping("/export/users")
public void exportUsers(HttpServletResponse response) {
    List<User> users = userService.findActiveUsers();
    
    ExcelExporter.builder(User.class)
        .toWeb(response, "活躍用戶")
        .headers("ID", "用戶名", "注冊郵箱", "最后登錄時(shí)間")
        .data(users, user -> Arrays.asList(
            user.getId(),
            user.getUsername(),
            user.getEmail(),
            formatDateTime(user.getLastLogin())
        ))
        .execute();
}

// 日期格式化輔助方法
private String formatDateTime(LocalDateTime dateTime) {
    return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
}

場景2:銷售報(bào)表生成(文件導(dǎo)出)

public void generateDailySalesReport() {
    List<SalesRecord> records = salesService.getYesterdayRecords();
    
    ExcelExporter.builder(SalesRecord.class)
        .toFile("/reports/sales", "日銷售報(bào)告")
        .withoutDateSuffix() // 禁用日期后綴
        .sheetName("銷售明細(xì)")
        .mainTitle("昨日銷售匯總")
        .headers("銷售員", "產(chǎn)品", "數(shù)量", "單價(jià)", "總金額")
        .data(records, record -> Arrays.asList(
            record.getSalesperson(),
            record.getProductName(),
            record.getQuantity(),
            record.getUnitPrice(),
            record.getTotalAmount()
        ))
        .batchSize(10000) // 大數(shù)據(jù)量優(yōu)化
        .execute();
}

場景3:財(cái)務(wù)數(shù)據(jù)導(dǎo)出(自定義樣式)

public void exportFinancialReport(HttpServletResponse response) {
    // 創(chuàng)建專業(yè)財(cái)務(wù)樣式
    WriteCellStyle moneyStyle = createMoneyStyle();
    WriteCellStyle headerStyle = createFinanceHeaderStyle();
    
    List<FinancialData> data = financeService.getQuarterlyReport();
    
    ExcelExporter.builder(FinancialData.class)
        .toWeb(response, "Q1財(cái)務(wù)報(bào)表")
        .headers("科目", "1月", "2月", "3月", "季度合計(jì)")
        .data(data, item -> Arrays.asList(
            item.getAccount(),
            item.getJanuary(),
            item.getFebruary(),
            item.getMarch(),
            item.getQuarterTotal()
        ))
        .customStyle(
            createFinanceTitleStyle(),
            headerStyle,
            moneyStyle
        )
        .execute();
}

// 創(chuàng)建貨幣樣式
private WriteCellStyle createMoneyStyle() {
    WriteCellStyle style = new WriteCellStyle();
    style.setDataFormat((short) 4); // 貨幣格式
    style.setHorizontalAlignment(HorizontalAlignment.RIGHT);
    WriteFont font = new WriteFont();
    font.setColor(IndexedColors.DARK_GREEN.getIndex());
    style.setWriteFont(font);
    return style;
}

性能優(yōu)化策略

1. 分批次寫入 - 內(nèi)存控制

private static void writeDataInBatches(ExcelWriter excelWriter, 
                                      WriteSheet writeSheet,
                                      List<List<Object>> data,
                                      int batchSize) {
    int total = data.size();
    int pages = (int) Math.ceil((double) total / batchSize);

    for (int i = 0; i < pages; i++) {
        int fromIndex = i * batchSize;
        int toIndex = Math.min((i + 1) * batchSize, total);
        excelWriter.write(data.subList(fromIndex, toIndex), writeSheet);
    }
}

這種分批處理機(jī)制確保即使導(dǎo)出百萬級數(shù)據(jù),內(nèi)存占用也能保持穩(wěn)定。

2. 樣式復(fù)用 - 提升性能

// 樣式對象池
public class StylePool {
    private static final Map<String, WriteCellStyle> pool = new ConcurrentHashMap<>();
    
    public static WriteCellStyle getStyle(String key, Supplier<WriteCellStyle> creator) {
        return pool.computeIfAbsent(key, k -> creator.get());
    }
}

// 使用樣式池
WriteCellStyle headerStyle = StylePool.getStyle("financeHeader", () -> {
    WriteCellStyle style = new WriteCellStyle();
    // ... 樣式配置
    return style;
});

通過復(fù)用樣式對象,避免重復(fù)創(chuàng)建,顯著提升導(dǎo)出性能。

3. 異步導(dǎo)出 - 避免阻塞

@GetMapping("/export/large")
public ResponseEntity<Void> exportLargeData() {
    CompletableFuture.runAsync(() -> {
        ExcelExporter.builder(Data.class)
            .toFile("/reports", "bigdata")
            .data(largeData, ...)
            .execute();
    }, taskExecutor);
    
    return ResponseEntity.accepted()
            .header("Location", "/export/status/123")
            .build();
}

結(jié)合Spring的異步處理,避免大文件導(dǎo)出阻塞主線程。

擴(kuò)展功能實(shí)現(xiàn)

多級表頭支持

private List<List<String>> prepareMultiLevelHeaders() {
    List<List<String>> headList = new ArrayList<>();
    
    // 第一級:主標(biāo)題(跨所有列)
    headList.add(Collections.singletonList("2024年度銷售分析報(bào)告"));
    
    // 第二級:分類標(biāo)題
    headList.add(Arrays.asList("基本信息", "", "業(yè)績指標(biāo)"));
    
    // 第三級:詳細(xì)列名
    headList.add(Arrays.asList("區(qū)域", "銷售員", "銷售額", "同比增長", "目標(biāo)達(dá)成率"));
    
    return headList;
}

動態(tài)列寬調(diào)整

// 在customStyle后添加列寬策略
builder.customStyle(...)
       .registerColumnWidthHandler((sheet, cell, head, relativeRowIndex, isHead) -> {
           if (cell.getColumnIndex() == 2) {
               sheet.setColumnWidth(cell.getColumnIndex(), 20 * 256); // 20字符寬
           }
       });

重構(gòu)前后對比分析

維度傳統(tǒng)實(shí)現(xiàn)構(gòu)建器模式重構(gòu)
方法參數(shù)5-6個(gè)必要參數(shù)鏈?zhǔn)脚渲?,按需設(shè)置
代碼可讀性參數(shù)順序依賴,可讀性差自描述鏈?zhǔn)秸{(diào)用,清晰明了
擴(kuò)展性新增參數(shù)需修改方法簽名添加構(gòu)建器方法,不影響現(xiàn)有調(diào)用
默認(rèn)配置需要多個(gè)重載方法內(nèi)置智能默認(rèn)值
使用一致性Web/文件導(dǎo)出接口不同統(tǒng)一流暢API
異常處理分散在各處集中入口校驗(yàn)
維護(hù)成本高(修改影響大)低(內(nèi)部封裝)

最佳實(shí)踐與總結(jié)

實(shí)施建議

  • 樣式標(biāo)準(zhǔn)化:在企業(yè)內(nèi)部建立統(tǒng)一的樣式規(guī)范,通過customStyle實(shí)現(xiàn)復(fù)用
  • 模板化配置:對常用導(dǎo)出場景創(chuàng)建配置模板
  • 異步處理:大數(shù)據(jù)量導(dǎo)出務(wù)必使用異步機(jī)制
  • 監(jiān)控集成:添加導(dǎo)出耗時(shí)和成功率監(jiān)控
  • 權(quán)限控制:敏感數(shù)據(jù)導(dǎo)出添加權(quán)限驗(yàn)證

總結(jié)

通過構(gòu)建器模式重構(gòu)Excel導(dǎo)出工具類,我們實(shí)現(xiàn)了:

  • 參數(shù)精簡:消除多參數(shù)問題,提升API整潔度
  • 調(diào)用優(yōu)雅:鏈?zhǔn)紸PI使代碼更易讀寫
  • 擴(kuò)展靈活:新增配置不影響現(xiàn)有代碼
  • 性能優(yōu)化:分批處理支持大數(shù)據(jù)量
  • 體驗(yàn)統(tǒng)一:Web/文件導(dǎo)出一致API

重構(gòu)后的工具類不僅解決了參數(shù)過多的問題,更通過流暢接口設(shè)計(jì)提升了開發(fā)體驗(yàn)。這種模式不僅適用于Excel導(dǎo)出,也可推廣到其他需要復(fù)雜配置的場景,如PDF導(dǎo)出、文件上傳等。

以上就是Java利用構(gòu)建器模式重構(gòu)Excel導(dǎo)出工具類的詳細(xì)內(nèi)容,更多關(guān)于Java重構(gòu)Excel導(dǎo)出工具類的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論