Java Excel 導出:EasyExcel 使用詳解

  • 一、什麼是 EasyExcel
  • 二、Maven 依賴安裝
  • 三、Excel 導出工具類設計
  • 3.1 設計目標與整體架構
  • 3.2 完整工具類:ExportTool.java
  • 3.3 核心方法分析
  • 3.3.1 簡單導出方法
  • 3.3.2 帶樣式導出方法
  • 3.4 樣式策略分析
  • 3.4.1 表頭樣式
  • 3.4.2 內容樣式
  • 3.5 列寬自適應策略
  • 3.6 輸出流包裝策略
  • 3.7 ExcelWriter 構建與寫入
  • 四、導出對象類設計
  • 五、示例 Controller 導出方法

在 Java 後端開發中,Excel 導出是非常常見的需求,涉及報表、數據分析或業務導出。

本文將結合 阿里 EasyExcel 庫,詳細講解 Excel 導出流程,包括工具類設計、樣式、列寬策略、對象映射、輸出流處理


一、什麼是 EasyExcel

EasyExcel 是阿里巴巴開源的 Java Excel 處理庫,相比 Apache POI 或 JXL:

  1. 高性能
  • 基於 SAX 流式解析和寫入,內存佔用低
  • 可以輕鬆處理百萬行數據導出,不會 OOM
  1. 易用
  • 註解式對象映射(@ExcelProperty
  • 只需定義對象類即可生成 Excel 表格
  1. 樣式靈活
  • 表頭樣式、內容樣式、列寬、合併單元格等都可自定義
  1. Web 集成方便
  • 可以直接輸出到瀏覽器下載,無需先生成文件

適合企業後台系統、報表系統、統計平台等場景。


二、Maven 依賴安裝

pom.xml 中引入 EasyExcel:

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>

注意事項:

  1. EasyExcel 依賴 JDK 1.8 及以上版本
  2. 在實際項目中,如果出現依賴衝突或版本不兼容導致導出異常,可參考該文章進行排查和解決:EasyExcel 依賴衝突解決方案。

三、Excel 導出工具類設計

3.1 設計目標與整體架構

ExportTool 工具類主要實現了兩個導出方式:

  1. 簡單導出:快速將數據列表寫入 Excel,無樣式、無列寬控制。
  2. 帶樣式導出:支持自定義表頭樣式、內容樣式和列寬自適應。

核心設計特點

  • 泛型支持:通過 <T> + Class<T>,工具類可導出任意類型的數據對象。
  • 流安全處理:通過 NoCloseOutputStream 包裝輸出流,避免 EasyExcel 自動關閉 HttpServletResponse
  • 策略模式:樣式、列寬分別封裝為獨立策略,便於擴展。
  • 日誌和異常管理:導出過程中的異常統一記錄並封裝為 ServiceException,方便上層調用處理。

工具類結構可以理解為三層:

響應流處理 → ExcelWriter 構建 → 樣式 & 列寬策略 → 數據寫入

3.2 完整工具類:ExportTool.java

@Slf4j
public class ExportTool {
/**
* 導出 Excel 文件(簡單導出)
*
* @param dataList  導出的數據列表
* @param clazz     數據類型(Excel 實體類)
* @param fileName  導出的文件名(不帶擴展名)
* @param <T>       數據泛型
*/
public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName) {
  if (dataList == null || dataList.isEmpty()) {
  log.warn("[ExportTool] 導出數據為空: {}", fileName);
  }
  try {
  HttpServletResponse response = WebTool.getResponse();
  // 使用複用的響應流設置方法
  setupResponse(response, fileName + ".xlsx");
  try (ServletOutputStream out = response.getOutputStream()) {
  // 簡單導出,不增加樣式或列寬限制
  EasyExcel.write(out, clazz)
  .autoCloseStream(true)
  .sheet("數據")
  .doWrite(dataList);
  }
  log.info("[ExportTool] 導出成功: {}, 共 {} 條記錄", fileName, dataList.size());
  } catch (Exception e) {
  log.error("[ExportTool] 導出失敗: {}", fileName, e);
  throw new ServiceException("導出失敗,請稍後重試");
  }
  }
  /**
  * 導出 Excel 文件(帶表頭樣式)
  *
  * @param dataList 數據列表
  * @param clazz    數據類型(Excel 實體類)
  * @param fileName 導出文件名(包含 .xlsx)
  * @param <T>      數據類型
  */
  public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName) {
    HttpServletResponse response = WebTool.getResponse();
    try {
    // 設置響應頭
    setupResponse(response, fileName);
    // 獲取輸出流並防止 EasyExcel 關閉響應流
    ServletOutputStream out = response.getOutputStream();
    NoCloseOutputStream noCloseOut = new NoCloseOutputStream(out);
    // 創建 ExcelWriter
    ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)
    // 註冊樣式策略
    .registerWriteHandler(new HorizontalCellStyleStrategy(createHeadStyle(), createContentStyle()))
    // 註冊列寬自適應策略
    .registerWriteHandler(createAutoWidthStrategy())
    .build();
    // 創建 Sheet
    WriteSheet writeSheet = EasyExcel.writerSheet("導出數據").build();
    // 寫入數據
    excelWriter.write(dataList, writeSheet);
    // 完成寫入
    excelWriter.finish();
    log.info("[ExportTool] 導出成功: {}", fileName);
    } catch (Exception e) {
    log.error("[ExportTool] 導出失敗: {}", fileName, e);
    throw new ServiceException("導出 Excel 失敗");
    }
    }
    /**
    * 設置 HttpServletResponse 響應頭
    */
    private static void setupResponse(HttpServletResponse response, String fileName) throws Exception {
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
    response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
    }
    /**
    * 創建表頭樣式
    */
    private static WriteCellStyle createHeadStyle() {
    WriteCellStyle headStyle = new WriteCellStyle();
    // 設置白底(不填充顏色)
    headStyle.setFillForegroundColor(null);
    headStyle.setFillPatternType(FillPatternType.NO_FILL);
    // 設置字體
    WriteFont headFont = new WriteFont();
    headFont.setFontHeightInPoints((short) 11);
    headFont.setFontName("微軟雅黑");
    headFont.setBold(true);
    headFont.setColor(IndexedColors.PALE_BLUE.getIndex());
    headStyle.setWriteFont(headFont);
    // 設置居中與自動換行
    headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
    headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    headStyle.setWrapped(true);
    // 設置無邊框
    headStyle.setBorderLeft(BorderStyle.NONE);
    headStyle.setBorderRight(BorderStyle.NONE);
    headStyle.setBorderTop(BorderStyle.NONE);
    headStyle.setBorderBottom(BorderStyle.NONE);
    return headStyle;
    }
    /**
    * 創建內容樣式
    */
    private static WriteCellStyle createContentStyle() {
    WriteCellStyle contentStyle = new WriteCellStyle();
    // 設置字體
    WriteFont contentFont = new WriteFont();
    contentFont.setFontHeightInPoints((short) 11);
    contentFont.setFontName("等線");
    contentStyle.setWriteFont(contentFont);
    // 內容右對齊,垂直居中,自動換行
    contentStyle.setHorizontalAlignment(HorizontalAlignment.RIGHT);
    contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    contentStyle.setWrapped(true);
    return contentStyle;
    }
    /**
    * 列寬自適應策略(根據內容長度自動調整列寬,限制最小 15、最大 20 字符)
    */
    private static AbstractColumnWidthStyleStrategy createAutoWidthStrategy() {
    return new AbstractColumnWidthStyleStrategy() {
    private final Map<Integer, Integer> columnWidthMap = new HashMap<>();
      @Override
      protected void setColumnWidth(WriteSheetHolder writeSheetHolder,
      List<WriteCellData<?>> cellDataList,
        Cell cell,
        Head head,
        Integer relativeRowIndex,
        Boolean isHead) {
        if (cell == null || cell.getCellType() != CellType.STRING) return;
        String value = cell.getStringCellValue();
        if (value == null) return;
        Sheet sheet = writeSheetHolder.getSheet();
        int columnIndex = cell.getColumnIndex();
        // 根據 UTF-8 字節長度計算列寬
        int length = value.getBytes(StandardCharsets.UTF_8).length;
        int minWidth = 15 * 256;
        int maxWidth = 20 * 256;
        int width = Math.max(minWidth, Math.min(length * 256 + 200, maxWidth));
        // 如果當前列寬大於記錄的最大寬度,則更新列寬
        Integer maxWidthRecorded = columnWidthMap.getOrDefault(columnIndex, 0);
        if (width > maxWidthRecorded) {
        columnWidthMap.put(columnIndex, width);
        sheet.setColumnWidth(columnIndex, width);
        }
        // 設置自動換行樣式
        CellStyle style = sheet.getWorkbook().createCellStyle();
        style.cloneStyleFrom(cell.getCellStyle());
        style.setWrapText(true);
        cell.setCellStyle(style);
        }
        };
        }
        /**
        * 包裝 Servlet 輸出流,不關閉底層流
        */
        public static class NoCloseOutputStream extends FilterOutputStream {
        public NoCloseOutputStream(OutputStream out) {
        super(out);
        }
        @Override
        public void close() throws IOException {
        flush(); // 不關閉底層流,僅刷新
        }
        }
        }

3.3 核心方法分析

3.3.1 簡單導出方法

public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName)

功能:快速生成 Excel 文件,無樣式和列寬控制。

執行流程

  1. 數據判空:日誌告警數據為空情況。
  2. 獲取響應流:通過 WebTool.getResponse() 獲取 HttpServletResponse。
  3. 設置響應頭:設置 MIME 類型和 Content-Disposition,保證瀏覽器下載文件。
  4. EasyExcel 寫入:調用 EasyExcel.write(out, clazz).sheet("數據").doWrite(dataList),直接寫入數據列表。
  5. 異常處理:統一捕獲異常,拋出 ServiceException

特點

  • 接口簡單,使用泛型可適配任意實體類
  • 無額外樣式處理,適合數據量大或快速導出場景

3.3.2 帶樣式導出方法

public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName)

功能:在 Excel 中生成 自定義表頭、內容樣式,並自動調整列寬。

執行流程

  1. 獲取響應流並設置響應頭。
  2. 包裝輸出流:使用 NoCloseOutputStream 防止 EasyExcel 自動關閉底層流。
  3. 構建 ExcelWriter
  • 註冊 表頭樣式 + 內容樣式 (HorizontalCellStyleStrategy)
  • 註冊 列寬自適應策略 (AbstractColumnWidthStyleStrategy)
  1. 創建 Sheet:定義名稱,例如 "導出數據"
  2. 寫入數據excelWriter.write(dataList, writeSheet)
  3. 完成寫入:調用 excelWriter.finish() 完成 Excel 寫入流程。
  4. 異常處理:統一日誌記錄,拋出 ServiceException

特點

  • 樣式和列寬可獨立調整,便於複用和擴展
  • 保證瀏覽器端下載體驗不受影響
  • 對長文本和數字型列提供自動換行和右對齊

3.4 樣式策略分析

3.4.1 表頭樣式

WriteCellStyle headStyle = createHeadStyle();

特點

  • 字體:微軟雅黑,加粗,顏色淡藍
  • 對齊方式:水平居中、垂直居中
  • 自動換行,保證多行表頭顯示完整
  • 無邊框、白底,視覺簡潔

設計思路

  • 獨立封裝表頭樣式,便於在多個導出場景中複用
  • 樣式可通過 HorizontalCellStyleStrategy 與內容樣式統一管理

3.4.2 內容樣式

WriteCellStyle contentStyle = createContentStyle();

特點

  • 字體:等線
  • 水平右對齊(適合數字、金額)、垂直居中
  • 自動換行,保證長文本顯示
  • 與表頭樣式區分,形成信息層次感

設計思路

  • 獨立封裝內容樣式,提高可維護性
  • 通過策略模式應用於 ExcelWriter,無需重複設置

3.5 列寬自適應策略

AbstractColumnWidthStyleStrategy autoWidthStrategy = createAutoWidthStrategy();

實現邏輯

  1. 遍歷每個單元格內容,獲取 UTF-8 字節長度。
  2. 根據長度計算列寬,設置最小 15、最大 20 字符。
  3. 對比歷史最大列寬,確保列寬不會縮小。
  4. 設置單元格自動換行,保證內容完整顯示。

優點

  • 避免手動設置每列寬度
  • 支持中英文混合內容,保證可讀性
  • 與樣式策略解耦,可獨立替換或增強

注意:UTF-8 字節長度計算對不同字符可能略有偏差,中英文混合時列寬可能需微調


3.6 輸出流包裝策略

public static class NoCloseOutputStream extends FilterOutputStream

作用

  • EasyExcel 在 finish() 默認關閉流
  • 包裝流後僅執行 flush(),不關閉底層 HttpServletResponse
  • 保證瀏覽器下載文件正常,不中斷響應

優點

  • 簡單、安全
  • 兼容大多數 Web 導出場景

3.7 ExcelWriter 構建與寫入

ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)
.registerWriteHandler(styleStrategy)
.registerWriteHandler(autoWidthStrategy)
.build();

分析

  • 解耦樣式和列寬策略,便於單獨擴展
  • 批量寫入數據,可適配大數據場景
  • 支持自定義 Sheet 名稱
  • 可進一步擴展:凍結首行、單元格合併、插入公式等

四、導出對象類設計

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExportUserVO {
@ExcelProperty("姓名")
private String userName;
@ExcelProperty("ID")
private Long id;
@ExcelProperty("使用個數")
private Integer useCount;
}
  • 每個字段使用 @ExcelProperty 註解列名
  • 數據類型支持 String、Integer、Double、Long 等常用類型
  • 對象類清晰定義列名和數據結構

五、示例 Controller 導出方法

@GetMapping("/export")
public void exportUserExcel(HttpServletResponse response) {
List<ExportUserVO> exportList = Arrays.asList(
  new ExportUserVO("張三", 1001L, 5),
  new ExportUserVO("李四", 1002L, 3)
  );
  String fileName = "用户導出.xlsx";
  ExportTool.exportExcelWithStyle(response, exportList, ExportUserVO.class, fileName);
  }
  • 瀏覽器訪問 /export 即可直接下載 Excel
  • 充分體現工具類的通用性和可複用性

導出Excel效果

深入解析:Java Excel 導出:EasyExcel 使用詳解_List