杰瑞科技汇

java web 导出excel

核心思路

无论使用哪种技术,导出 Excel 的核心流程基本一致:

java web 导出excel-图1
(图片来源网络,侵删)
  1. 前端:用户点击一个“导出”按钮。
  2. 后端
    • 接收到导出请求。
    • 从数据库或其他数据源查询出需要导出的数据。
    • 使用 Excel 操作库(如 Apache POI、EasyExcel)创建一个 Excel 文档。
    • 将这个 Excel 文档以文件流的形式写回到 HTTP 响应中。
    • 设置正确的 HTTP 响应头,告诉浏览器这是一个需要下载的文件。
  3. 浏览器:接收到响应后,根据 Content-TypeContent-Disposition 头信息,自动弹出下载对话框,让用户保存 Excel 文件。

主流方案对比

在选择技术方案时,主要考虑以下几点:

方案 优点 缺点 适用场景
Apache POI 功能最强大,支持所有 Excel 版本(.xls, .xlsx),能操作复杂的样式、图表、公式。 API 繁琐,代码量大;对于大数据量,内存消耗巨大,容易导致 OOM(内存溢出)。 需要精细控制样式、处理复杂文件格式的场景。
EasyExcel (阿里) API 简洁,基于 POI 封装;解决了 POI 大数据量内存问题,采用 SAX 模式流式读写,内存占用极低。 功能相比 POI 稍弱,不支持复杂的图表和公式。 强烈推荐,绝大多数 Web 导出场景,尤其是大数据量导出。
JXLS (模板引擎) 通过定义 Excel 模板文件,将数据填充到模板中,逻辑与视图分离,非常灵活。 依赖模板文件,灵活性受限于模板;对动态表头、合并单元格等复杂操作支持不如 POI/EasyExcel。 报表格式固定、样式复杂,且希望业务逻辑与 Excel 样式解耦的场景。

对于绝大多数 Java Web 项目,EasyExcel 是目前的首选方案,它在易用性和性能之间取得了最佳平衡。


使用 EasyExcel (推荐)

EasyExcel 是阿里巴巴开源的一个优秀的处理 Excel 的开源项目,官网文档非常完善。

添加 Maven 依赖

在你的 pom.xml 文件中添加 EasyExcel 的依赖。

java web 导出excel-图2
(图片来源网络,侵删)
<dependencies>
    <!-- 其他依赖... -->
    <!-- EasyExcel 依赖 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.3.2</version> <!-- 建议使用最新版本 -->
    </dependency>
    <!-- 为了方便演示,添加 Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

准备数据模型

创建一个与 Excel 表头对应的 Java 实体类,使用 @ExcelProperty 注解来指定表头。

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
// @Data 是 Lombok 注解,自动生成 getter, setter, toString 等
public class UserData {
    // value 指定表头名称,index 指定列的顺序(可选)
    @ExcelProperty("用户ID")
    private Integer id;
    @ExcelProperty("用户名")
    private String username;
    @ExcelProperty("邮箱")
    private String email;
    @ExcelProperty("创建时间")
    private String createTime;
}

编写 Controller

创建一个 Controller 来处理前端的导出请求。

import com.alibaba.excel.EasyExcel;
import com.example.demo.model.UserData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ExcelExportController {
    @GetMapping("/export")
    public void export(HttpServletResponse response) throws IOException {
        // 1. 设置响应头
        // 设置内容类型为 application/vnd.ms-excel,表示这是一个 Excel 文件
        response.setContentType("application/vnd.ms-excel");
        // 设置编码,防止中文乱码
        response.setCharacterEncoding("utf-8");
        // 设置下载文件名,URLEncoder.encode 用于处理文件名中的中文和空格
        String fileName = URLEncoder.encode("用户数据", "UTF-8");
        // attachment 表示以附件形式下载,filename="..." 指定下载的文件名
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        // 2. 查询数据 (这里用模拟数据代替)
        List<UserData> dataList = getDataList();
        // 3. 写入 Excel
        // EasyExcel.write(输出流, 数据模型.class).sheet("工作表名").doWrite(数据列表);
        EasyExcel.write(response.getOutputStream(), UserData.class)
                 .sheet("用户信息") // 指定 sheet 名称
                 .doWrite(dataList); // 写入数据
    }
    // 模拟从数据库查询数据
    private List<UserData> getDataList() {
        List<UserData> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            UserData data = new UserData();
            data.setId(i);
            data.setUsername("用户" + i);
            data.setEmail("user" + i + "@example.com");
            data.setCreateTime(new Date().toString());
            list.add(data);
        }
        return list;
    }
}

前端调用

你可以使用简单的 HTML + JavaScript 来测试。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">Excel导出示例</title>
</head>
<body>
    <h1>点击下方按钮导出 Excel</h1>
    <button onclick="exportExcel()">导出用户数据</button>
    <script>
        function exportExcel() {
            // 使用 window.open 或 fetch 发起 GET 请求
            // window.location.href 也可以,但可能会影响当前页面
            window.open('http://localhost:8080/excel/export', '_blank');
        }
    </script>
</body>
</html>

启动你的 Spring Boot 应用,访问这个 HTML 页面,点击按钮,浏览器就会自动下载名为 "用户数据.xlsx" 的文件。

java web 导出excel-图3
(图片来源网络,侵删)

使用 Apache POI (传统方案)

如果你需要使用 POI,或者项目历史代码中已经使用了 POI,可以参考下面的实现。

添加 Maven 依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>

修改 Controller (使用 POI)

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ExcelExportControllerPoi {
    @GetMapping("/export-poi")
    public void exportWithPoi(HttpServletResponse response) throws IOException {
        // 1. 设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("用户数据_POI", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        // 2. 创建工作簿 (XSSFWorkbook 用于 .xlsx 格式)
        Workbook workbook = new XSSFWorkbook();
        // 3. 创建工作表
        Sheet sheet = workbook.createSheet("用户信息");
        // 4. 创建表头
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("用户ID");
        headerRow.createCell(1).setCellValue("用户名");
        headerRow.createCell(2).setCellValue("邮箱");
        headerRow.createCell(3).setCellValue("创建时间");
        // 5. 查询数据
        List<UserData> dataList = getDataList();
        // 6. 写入数据
        int rowNum = 1;
        for (UserData data : dataList) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(data.getId());
            row.createCell(1).setCellValue(data.getUsername());
            row.createCell(2).setCellValue(data.getEmail());
            row.createCell(3).setCellValue(data.getCreateTime());
        }
        // 7. 将工作簿写入输出流
        workbook.write(response.getOutputStream());
        // 8. 关闭工作簿,释放资源
        workbook.close();
    }
    // 复用上面的模拟数据方法
    private List<UserData> getDataList() {
        // ... 同上 ...
    }
}

高级特性与最佳实践

大数据量导出 (避免 OOM)

对于 POI,如果数据量很大(比如几十万行),直接将所有数据加载到内存中创建 Workbook 会导致内存溢出。

解决方案:使用 SXSSFWorkbook (POI 的 Streaming Usermodel)

SXSSFWorkbook 是 POI 提供的流式 API,它会在内存中只保留一定数量的行,超过的部分会自动写入临时磁盘文件,从而极大地降低内存消耗。

// 在 POI Controller 中修改
import org.apache.poi.xssf.streaming.SXSSFWorkbook; // 注意这个包
// ...
// 2. 创建 SXSSFWorkbook
// 参数 100 表示在内存中保留的行数,超过的会刷新到磁盘
Workbook workbook = new SXSSFWorkbook(100); 
Sheet sheet = workbook.createSheet("用户信息");
// ... 后续代码与普通 POI 几乎一样 ...

对于 EasyExcel,它底层就采用了类似 SAX 的流式读取/写入机制,所以天生就支持大数据量导出,你不需要做任何特殊处理,直接调用 doWrite 即可,它会非常高效且低内存消耗。

复杂样式(加粗、合并单元格等)

EasyExcel 提供了 WriteCellStyle 来设置样式。

// 在 EasyExcel.write(...) 之后链式调用 .registerWriteHandler(...)
EasyExcel.write(response.getOutputStream(), UserData.class)
         .sheet("用户信息")
         .registerWriteHandler(new HorizontalCellStyleStrategy()) // 注册样式处理器
         .doWrite(dataList);
// 自定义样式处理器
public class HorizontalCellStyleStrategy extends AbstractCellWriteHandler {
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 只处理表头
        if (isHead) {
            // 获取工作簿
            Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
            // 创建样式
            CellStyle style = workbook.createCellStyle();
            // 创建字体
            Font font = workbook.createFont();
            font.setBold(true); // 加粗
            style.setFont(font);
            // 应用样式到单元格
            cell.setCellStyle(style);
        }
    }
}

动态表头

有时表头不是固定的,需要根据业务动态生成。

EasyExcel 支持通过 Head 对象动态构建表头。

// 在 Controller 中
List<List<String>> headList = new ArrayList<>();
headList.add(List.of("第一层", "第一层"));
headList.add(List.of("第二层", "用户名")); // 合并单元格后的实际表头
List<List<Object>> dataList = new ArrayList<>();
// ... 准备动态数据,数据结构要与表头对应 ...
EasyExcel.write(response.getOutputStream())
         .head(headList) // 设置动态表头
         .sheet("动态表头")
         .doWrite(dataList);

性能优化建议

  • 关闭自动列宽sheet.autoSizeColumn() 会遍历所有数据来计算宽度,非常耗时,如果列数不多或宽度固定,可以手动设置列宽或直接跳过。
  • 减少样式对象创建:样式对象(CellStyle)是与 Workbook 关联的,创建过多会消耗内存,尽量复用同一个样式对象。
  • 选择合适的技术:再次强调,导出优先用 EasyExcel,它已经为你处理好了大部分性能问题。

希望这份详细的指南能帮助你顺利实现 Java Web 中的 Excel 导出功能!

分享:
扫描分享到社交APP
上一篇
下一篇