Java使用EasyExcel導(dǎo)出Excel的全流程詳解
引言
在 Java 后端開發(fā)中,Excel 導(dǎo)出是非常常見的需求,涉及報(bào)表、數(shù)據(jù)分析或業(yè)務(wù)導(dǎo)出。
本文將結(jié)合 阿里 EasyExcel 庫,詳細(xì)講解 Excel 導(dǎo)出流程,包括工具類設(shè)計(jì)、樣式、列寬策略、對(duì)象映射、輸出流處理。
一、什么是 EasyExcel
EasyExcel 是阿里巴巴開源的 Java Excel 處理庫,相比 Apache POI 或 JXL:
高性能
- 基于 SAX 流式解析和寫入,內(nèi)存占用低
- 可以輕松處理百萬行數(shù)據(jù)導(dǎo)出,不會(huì) OOM
易用
- 注解式對(duì)象映射(
@ExcelProperty) - 只需定義對(duì)象類即可生成 Excel 表格
樣式靈活
- 表頭樣式、內(nèi)容樣式、列寬、合并單元格等都可自定義
Web 集成方便
- 可以直接輸出到瀏覽器下載,無需先生成文件
適合企業(yè)后臺(tái)系統(tǒng)、報(bào)表系統(tǒng)、統(tǒng)計(jì)平臺(tái)等場景。
二、Maven 依賴安裝
在 pom.xml 中引入 EasyExcel:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
注意事項(xiàng):
- EasyExcel 依賴 JDK 1.8 及以上版本。
- 在實(shí)際項(xiàng)目中,如果出現(xiàn)依賴沖突或版本不兼容導(dǎo)致導(dǎo)出異常,可參考該文章進(jìn)行排查和解決:EasyExcel 依賴沖突解決方案。
三、Excel 導(dǎo)出工具類設(shè)計(jì)
3.1 設(shè)計(jì)目標(biāo)與整體架構(gòu)
ExportTool 工具類主要實(shí)現(xiàn)了兩個(gè)導(dǎo)出方式:
- 簡單導(dǎo)出:快速將數(shù)據(jù)列表寫入 Excel,無樣式、無列寬控制。
- 帶樣式導(dǎo)出:支持自定義表頭樣式、內(nèi)容樣式和列寬自適應(yīng)。
核心設(shè)計(jì)特點(diǎn):
- 泛型支持:通過
<T>+Class<T>,工具類可導(dǎo)出任意類型的數(shù)據(jù)對(duì)象。 - 流安全處理:通過
NoCloseOutputStream包裝輸出流,避免 EasyExcel 自動(dòng)關(guān)閉HttpServletResponse。 - 策略模式:樣式、列寬分別封裝為獨(dú)立策略,便于擴(kuò)展。
- 日志和異常管理:導(dǎo)出過程中的異常統(tǒng)一記錄并封裝為
ServiceException,方便上層調(diào)用處理。
工具類結(jié)構(gòu)可以理解為三層:
響應(yīng)流處理 → ExcelWriter 構(gòu)建 → 樣式 & 列寬策略 → 數(shù)據(jù)寫入
3.2 完整工具類:ExportTool.java
@Slf4j
public class ExportTool {
/**
* 導(dǎo)出 Excel 文件(簡單導(dǎo)出)
*
* @param dataList 導(dǎo)出的數(shù)據(jù)列表
* @param clazz 數(shù)據(jù)類型(Excel 實(shí)體類)
* @param fileName 導(dǎo)出的文件名(不帶擴(kuò)展名)
* @param <T> 數(shù)據(jù)泛型
*/
public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName) {
if (dataList == null || dataList.isEmpty()) {
log.warn("[ExportTool] 導(dǎo)出數(shù)據(jù)為空: {}", fileName);
}
try {
HttpServletResponse response = WebTool.getResponse();
// 使用復(fù)用的響應(yīng)流設(shè)置方法
setupResponse(response, fileName + ".xlsx");
try (ServletOutputStream out = response.getOutputStream()) {
// 簡單導(dǎo)出,不增加樣式或列寬限制
EasyExcel.write(out, clazz)
.autoCloseStream(true)
.sheet("數(shù)據(jù)")
.doWrite(dataList);
}
log.info("[ExportTool] 導(dǎo)出成功: {}, 共 {} 條記錄", fileName, dataList.size());
} catch (Exception e) {
log.error("[ExportTool] 導(dǎo)出失敗: {}", fileName, e);
throw new ServiceException("導(dǎo)出失敗,請(qǐng)稍后重試");
}
}
/**
* 導(dǎo)出 Excel 文件(帶表頭樣式)
*
* @param dataList 數(shù)據(jù)列表
* @param clazz 數(shù)據(jù)類型(Excel 實(shí)體類)
* @param fileName 導(dǎo)出文件名(包含 .xlsx)
* @param <T> 數(shù)據(jù)類型
*/
public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName) {
HttpServletResponse response = WebTool.getResponse();
try {
// 設(shè)置響應(yīng)頭
setupResponse(response, fileName);
// 獲取輸出流并防止 EasyExcel 關(guān)閉響應(yīng)流
ServletOutputStream out = response.getOutputStream();
NoCloseOutputStream noCloseOut = new NoCloseOutputStream(out);
// 創(chuàng)建 ExcelWriter
ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)
// 注冊(cè)樣式策略
.registerWriteHandler(new HorizontalCellStyleStrategy(createHeadStyle(), createContentStyle()))
// 注冊(cè)列寬自適應(yīng)策略
.registerWriteHandler(createAutoWidthStrategy())
.build();
// 創(chuàng)建 Sheet
WriteSheet writeSheet = EasyExcel.writerSheet("導(dǎo)出數(shù)據(jù)").build();
// 寫入數(shù)據(jù)
excelWriter.write(dataList, writeSheet);
// 完成寫入
excelWriter.finish();
log.info("[ExportTool] 導(dǎo)出成功: {}", fileName);
} catch (Exception e) {
log.error("[ExportTool] 導(dǎo)出失敗: {}", fileName, e);
throw new ServiceException("導(dǎo)出 Excel 失敗");
}
}
/**
* 設(shè)置 HttpServletResponse 響應(yīng)頭
*/
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);
}
/**
* 創(chuàng)建表頭樣式
*/
private static WriteCellStyle createHeadStyle() {
WriteCellStyle headStyle = new WriteCellStyle();
// 設(shè)置白底(不填充顏色)
headStyle.setFillForegroundColor(null);
headStyle.setFillPatternType(FillPatternType.NO_FILL);
// 設(shè)置字體
WriteFont headFont = new WriteFont();
headFont.setFontHeightInPoints((short) 11);
headFont.setFontName("微軟雅黑");
headFont.setBold(true);
headFont.setColor(IndexedColors.PALE_BLUE.getIndex());
headStyle.setWriteFont(headFont);
// 設(shè)置居中與自動(dòng)換行
headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
headStyle.setWrapped(true);
// 設(shè)置無邊框
headStyle.setBorderLeft(BorderStyle.NONE);
headStyle.setBorderRight(BorderStyle.NONE);
headStyle.setBorderTop(BorderStyle.NONE);
headStyle.setBorderBottom(BorderStyle.NONE);
return headStyle;
}
/**
* 創(chuàng)建內(nèi)容樣式
*/
private static WriteCellStyle createContentStyle() {
WriteCellStyle contentStyle = new WriteCellStyle();
// 設(shè)置字體
WriteFont contentFont = new WriteFont();
contentFont.setFontHeightInPoints((short) 11);
contentFont.setFontName("等線");
contentStyle.setWriteFont(contentFont);
// 內(nèi)容右對(duì)齊,垂直居中,自動(dòng)換行
contentStyle.setHorizontalAlignment(HorizontalAlignment.RIGHT);
contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
contentStyle.setWrapped(true);
return contentStyle;
}
/**
* 列寬自適應(yīng)策略(根據(jù)內(nèi)容長度自動(dòng)調(diào)整列寬,限制最小 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();
// 根據(jù) UTF-8 字節(jié)長度計(jì)算列寬
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));
// 如果當(dāng)前列寬大于記錄的最大寬度,則更新列寬
Integer maxWidthRecorded = columnWidthMap.getOrDefault(columnIndex, 0);
if (width > maxWidthRecorded) {
columnWidthMap.put(columnIndex, width);
sheet.setColumnWidth(columnIndex, width);
}
// 設(shè)置自動(dòng)換行樣式
CellStyle style = sheet.getWorkbook().createCellStyle();
style.cloneStyleFrom(cell.getCellStyle());
style.setWrapText(true);
cell.setCellStyle(style);
}
};
}
/**
* 包裝 Servlet 輸出流,不關(guān)閉底層流
*/
public static class NoCloseOutputStream extends FilterOutputStream {
public NoCloseOutputStream(OutputStream out) {
super(out);
}
@Override
public void close() throws IOException {
flush(); // 不關(guān)閉底層流,僅刷新
}
}
}
3.3 核心方法分析
3.3.1 簡單導(dǎo)出方法
public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName)
功能:快速生成 Excel 文件,無樣式和列寬控制。
執(zhí)行流程:
- 數(shù)據(jù)判空:日志告警數(shù)據(jù)為空情況。
- 獲取響應(yīng)流:通過
WebTool.getResponse()獲取 HttpServletResponse。 - 設(shè)置響應(yīng)頭:設(shè)置 MIME 類型和 Content-Disposition,保證瀏覽器下載文件。
- EasyExcel 寫入:調(diào)用
EasyExcel.write(out, clazz).sheet("數(shù)據(jù)").doWrite(dataList),直接寫入數(shù)據(jù)列表。 - 異常處理:統(tǒng)一捕獲異常,拋出
ServiceException。
特點(diǎn):
- 接口簡單,使用泛型可適配任意實(shí)體類
- 無額外樣式處理,適合數(shù)據(jù)量大或快速導(dǎo)出場景
3.3.2 帶樣式導(dǎo)出方法
public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName)
功能:在 Excel 中生成 自定義表頭、內(nèi)容樣式,并自動(dòng)調(diào)整列寬。
執(zhí)行流程:
- 獲取響應(yīng)流并設(shè)置響應(yīng)頭。
- 包裝輸出流:使用
NoCloseOutputStream防止 EasyExcel 自動(dòng)關(guān)閉底層流。 - 構(gòu)建 ExcelWriter:
- 注冊(cè) 表頭樣式 + 內(nèi)容樣式 (
HorizontalCellStyleStrategy) - 注冊(cè) 列寬自適應(yīng)策略 (
AbstractColumnWidthStyleStrategy)
- 注冊(cè) 表頭樣式 + 內(nèi)容樣式 (
- 創(chuàng)建 Sheet:定義名稱,例如
"導(dǎo)出數(shù)據(jù)"。 - 寫入數(shù)據(jù):
excelWriter.write(dataList, writeSheet) - 完成寫入:調(diào)用
excelWriter.finish()完成 Excel 寫入流程。 - 異常處理:統(tǒng)一日志記錄,拋出
ServiceException。
特點(diǎn):
- 樣式和列寬可獨(dú)立調(diào)整,便于復(fù)用和擴(kuò)展
- 保證瀏覽器端下載體驗(yàn)不受影響
- 對(duì)長文本和數(shù)字型列提供自動(dòng)換行和右對(duì)齊
3.4 樣式策略分析
3.4.1 表頭樣式
WriteCellStyle headStyle = createHeadStyle();
特點(diǎn):
- 字體:
微軟雅黑,加粗,顏色淡藍(lán) - 對(duì)齊方式:水平居中、垂直居中
- 自動(dòng)換行,保證多行表頭顯示完整
- 無邊框、白底,視覺簡潔
設(shè)計(jì)思路:
- 獨(dú)立封裝表頭樣式,便于在多個(gè)導(dǎo)出場景中復(fù)用
- 樣式可通過
HorizontalCellStyleStrategy與內(nèi)容樣式統(tǒng)一管理
3.4.2 內(nèi)容樣式
WriteCellStyle contentStyle = createContentStyle();
特點(diǎn):
- 字體:
等線 - 水平右對(duì)齊(適合數(shù)字、金額)、垂直居中
- 自動(dòng)換行,保證長文本顯示
- 與表頭樣式區(qū)分,形成信息層次感
設(shè)計(jì)思路:
- 獨(dú)立封裝內(nèi)容樣式,提高可維護(hù)性
- 通過策略模式應(yīng)用于 ExcelWriter,無需重復(fù)設(shè)置
3.5 列寬自適應(yīng)策略
AbstractColumnWidthStyleStrategy autoWidthStrategy = createAutoWidthStrategy();
實(shí)現(xiàn)邏輯:
- 遍歷每個(gè)單元格內(nèi)容,獲取 UTF-8 字節(jié)長度。
- 根據(jù)長度計(jì)算列寬,設(shè)置最小 15、最大 20 字符。
- 對(duì)比歷史最大列寬,確保列寬不會(huì)縮小。
- 設(shè)置單元格自動(dòng)換行,保證內(nèi)容完整顯示。
優(yōu)點(diǎn):
- 避免手動(dòng)設(shè)置每列寬度
- 支持中英文混合內(nèi)容,保證可讀性
- 與樣式策略解耦,可獨(dú)立替換或增強(qiáng)
注意:UTF-8 字節(jié)長度計(jì)算對(duì)不同字符可能略有偏差,中英文混合時(shí)列寬可能需微調(diào)
3.6 輸出流包裝策略
public static class NoCloseOutputStream extends FilterOutputStream
作用:
- EasyExcel 在
finish()默認(rèn)關(guān)閉流 - 包裝流后僅執(zhí)行
flush(),不關(guān)閉底層HttpServletResponse - 保證瀏覽器下載文件正常,不中斷響應(yīng)
優(yōu)點(diǎn):
- 簡單、安全
- 兼容大多數(shù) Web 導(dǎo)出場景
3.7 ExcelWriter 構(gòu)建與寫入
ExcelWriter excelWriter = EasyExcel.write(noCloseOut, clazz)
.registerWriteHandler(styleStrategy)
.registerWriteHandler(autoWidthStrategy)
.build();
分析:
- 解耦樣式和列寬策略,便于單獨(dú)擴(kuò)展
- 批量寫入數(shù)據(jù),可適配大數(shù)據(jù)場景
- 支持自定義 Sheet 名稱
- 可進(jìn)一步擴(kuò)展:凍結(jié)首行、單元格合并、插入公式等
四、導(dǎo)出對(duì)象類設(shè)計(jì)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExportUserVO {
@ExcelProperty("姓名")
private String userName;
@ExcelProperty("ID")
private Long id;
@ExcelProperty("使用個(gè)數(shù)")
private Integer useCount;
}
- 每個(gè)字段使用
@ExcelProperty注解列名 - 數(shù)據(jù)類型支持 String、Integer、Double、Long 等常用類型
- 對(duì)象類清晰定義列名和數(shù)據(jù)結(jié)構(gòu)
五、示例 Controller 導(dǎo)出方法
@GetMapping("/export")
public void exportUserExcel(HttpServletResponse response) {
List<ExportUserVO> exportList = Arrays.asList(
new ExportUserVO("張三", 1001L, 5),
new ExportUserVO("李四", 1002L, 3)
);
String fileName = "用戶導(dǎo)出.xlsx";
ExportTool.exportExcelWithStyle(response, exportList, ExportUserVO.class, fileName);
}
- 瀏覽器訪問
/export即可直接下載 Excel - 充分體現(xiàn)工具類的通用性和可復(fù)用性
導(dǎo)出Excel效果:

以上就是Java使用EasyExcel導(dǎo)出Excel的全流程詳解的詳細(xì)內(nèi)容,更多關(guān)于Java EasyExcel導(dǎo)出Excel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot整合MD5加密完成注冊(cè)和登錄方式
MD5(MessageDigestAlgorithm5)是一種常見的哈希算法,用于生成固定長度(128位)的哈希值,主要應(yīng)用于數(shù)據(jù)完整性校驗(yàn)和密碼存儲(chǔ),MD5具有快速計(jì)算、不可逆性和抗碰撞性等特點(diǎn),盡管存在碰撞漏洞,MD5仍廣泛應(yīng)用于文件下載校驗(yàn)和數(shù)字簽名等場景2024-10-10
Java 重連機(jī)制的幾種實(shí)現(xiàn)方法
本文主要介紹了Java 重連機(jī)制的幾種實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-02-02
IDEA 程序包不存在,找不到符號(hào)但是明明存在對(duì)應(yīng)的jar包(問題分析及解決方案)
這篇文章主要介紹了IDEA 程序包不存在,找不到符號(hào)但是明明存在對(duì)應(yīng)的jar包 的解決方案,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
Java隨機(jī)值設(shè)置(java.util.Random類或Math.random方法)
在編程中有時(shí)我們需要生成一些隨機(jī)的字符串作為授權(quán)碼、驗(yàn)證碼等,以確保數(shù)據(jù)的安全性和唯一性,這篇文章主要給大家介紹了關(guān)于Java隨機(jī)值設(shè)置的相關(guān)資料,主要用的是java.util.Random類或Math.random()方法,需要的朋友可以參考下2024-08-08
解決使用this.getClass().getResource()獲取文件時(shí)遇到的坑
這篇文章主要介紹了解決使用this.getClass().getResource()獲取文件時(shí)遇到的坑問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
詳解Spring中實(shí)現(xiàn)接口動(dòng)態(tài)的解決方法
最近在工作遇到的一個(gè),發(fā)現(xiàn)網(wǎng)上的資料較少,所以想著總結(jié)分享下,下面這篇文章主要給大家介紹了關(guān)于Spring中實(shí)現(xiàn)接口動(dòng)態(tài)的解決方法,文中通過完整的示例代碼給大家介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
Bloc事件流是一個(gè)阻塞隊(duì)列結(jié)論解析
這篇文章主要為大家介紹了Bloc事件流是一個(gè)阻塞隊(duì)列結(jié)論解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

