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:
- 高性能
- 基於 SAX 流式解析和寫入,內存佔用低
- 可以輕鬆處理百萬行數據導出,不會 OOM
- 易用
- 註解式對象映射(
@ExcelProperty) - 只需定義對象類即可生成 Excel 表格
- 樣式靈活
- 表頭樣式、內容樣式、列寬、合併單元格等都可自定義
- Web 集成方便
- 可以直接輸出到瀏覽器下載,無需先生成文件
適合企業後台系統、報表系統、統計平台等場景。
二、Maven 依賴安裝
在 pom.xml 中引入 EasyExcel:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
注意事項:
- EasyExcel 依賴 JDK 1.8 及以上版本。
- 在實際項目中,如果出現依賴衝突或版本不兼容導致導出異常,可參考該文章進行排查和解決:EasyExcel 依賴衝突解決方案。
三、Excel 導出工具類設計
3.1 設計目標與整體架構
ExportTool 工具類主要實現了兩個導出方式:
- 簡單導出:快速將數據列表寫入 Excel,無樣式、無列寬控制。
- 帶樣式導出:支持自定義表頭樣式、內容樣式和列寬自適應。
核心設計特點:
- 泛型支持:通過
<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 文件,無樣式和列寬控制。
執行流程:
- 數據判空:日誌告警數據為空情況。
- 獲取響應流:通過
WebTool.getResponse()獲取 HttpServletResponse。 - 設置響應頭:設置 MIME 類型和 Content-Disposition,保證瀏覽器下載文件。
- EasyExcel 寫入:調用
EasyExcel.write(out, clazz).sheet("數據").doWrite(dataList),直接寫入數據列表。 - 異常處理:統一捕獲異常,拋出
ServiceException。
特點:
- 接口簡單,使用泛型可適配任意實體類
- 無額外樣式處理,適合數據量大或快速導出場景
3.3.2 帶樣式導出方法
public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName)
功能:在 Excel 中生成 自定義表頭、內容樣式,並自動調整列寬。
執行流程:
- 獲取響應流並設置響應頭。
- 包裝輸出流:使用
NoCloseOutputStream防止 EasyExcel 自動關閉底層流。 - 構建 ExcelWriter:
- 註冊 表頭樣式 + 內容樣式 (
HorizontalCellStyleStrategy) - 註冊 列寬自適應策略 (
AbstractColumnWidthStyleStrategy)
- 創建 Sheet:定義名稱,例如
"導出數據"。 - 寫入數據:
excelWriter.write(dataList, writeSheet) - 完成寫入:調用
excelWriter.finish()完成 Excel 寫入流程。 - 異常處理:統一日誌記錄,拋出
ServiceException。
特點:
- 樣式和列寬可獨立調整,便於複用和擴展
- 保證瀏覽器端下載體驗不受影響
- 對長文本和數字型列提供自動換行和右對齊
3.4 樣式策略分析
3.4.1 表頭樣式
WriteCellStyle headStyle = createHeadStyle();
特點:
- 字體:
微軟雅黑,加粗,顏色淡藍 - 對齊方式:水平居中、垂直居中
- 自動換行,保證多行表頭顯示完整
- 無邊框、白底,視覺簡潔
設計思路:
- 獨立封裝表頭樣式,便於在多個導出場景中複用
- 樣式可通過
HorizontalCellStyleStrategy與內容樣式統一管理
3.4.2 內容樣式
WriteCellStyle contentStyle = createContentStyle();
特點:
- 字體:
等線 - 水平右對齊(適合數字、金額)、垂直居中
- 自動換行,保證長文本顯示
- 與表頭樣式區分,形成信息層次感
設計思路:
- 獨立封裝內容樣式,提高可維護性
- 通過策略模式應用於 ExcelWriter,無需重複設置
3.5 列寬自適應策略
AbstractColumnWidthStyleStrategy autoWidthStrategy = createAutoWidthStrategy();
實現邏輯:
- 遍歷每個單元格內容,獲取 UTF-8 字節長度。
- 根據長度計算列寬,設置最小 15、最大 20 字符。
- 對比歷史最大列寬,確保列寬不會縮小。
- 設置單元格自動換行,保證內容完整顯示。
優點:
- 避免手動設置每列寬度
- 支持中英文混合內容,保證可讀性
- 與樣式策略解耦,可獨立替換或增強
注意: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效果: