Skip to content

PDF 与 Excel

Spring MVC 提供了内置支持来生成动态的 PDF 文件和 Excel 报表。这在生成账单、统计报告或数据导出场景中非常有用。由于 PDF 和 Excel 属于二进制文档,Spring 使用特殊的抽象视图类来处理这类渲染。

PDF 视图

Spring 的 PDF 支持基于 iText 2.1.7OpenPDF

实现步骤

  1. 创建视图类:继承 AbstractPdfView
  2. 实现 buildPdfDocument:在该方法中操作 PDF 对象。
java
public class UserPdfView extends AbstractPdfView {

    @Override
    protected void buildPdfDocument(Map<String, Object> model, Document document,
            PdfWriter writer, HttpServletRequest request, HttpServletResponse response) {

        List<User> users = (List<User>) model.get("users");
        
        document.add(new Paragraph("用户列表说明"));
        Table table = new Table(2);
        table.addCell("用户名");
        table.addCell("邮箱");
        
        for (User user : users) {
             table.addCell(user.getName());
             table.addCell(user.getEmail());
        }
        document.add(table);
    }
}
kotlin
class UserPdfView : AbstractPdfView() {

    override fun buildPdfDocument(model: Map<String, Any>, document: Document, 
                                 writer: PdfWriter, request: HttpServletRequest, 
                                 response: HttpServletResponse) {
        
        val users = model["users"] as List<User>
        document.add(Paragraph("用户列表说明"))
        
        val table = Table(2).apply {
            addCell("用户名")
            addCell("邮箱")
        }
        users.forEach { user ->
            table.addCell(user.name)
            table.addCell(user.email)
        }
        document.add(table)
    }
}

Excel 视图

Spring 的 Excel 支持基于 Apache POI 库。

视图类版本

  • AbstractXlsView: 使用经典的 .xls 格式(Excel 97-2003)。
  • AbstractXlsxView: 使用现代的 .xlsx 格式(推荐)。
  • AbstractXlsxStreamingView: 用于大数据量的流式导出,内存占用极低。

示例代码

java
public class UserExcelView extends AbstractXlsxView {

    @Override
    protected void buildExcelDocument(Map<String, Object> model, Workbook workbook,
            HttpServletRequest request, HttpServletResponse response) {

        Sheet sheet = workbook.createSheet("用户数据");
        Row header = sheet.createRow(0);
        header.createCell(0).setCellValue("ID");
        header.createCell(1).setCellValue("用户名");

        List<User> users = (List<User>) model.get("users");
        int rowNum = 1;
        for (User user : users) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(user.getId());
            row.createCell(1).setCellValue(user.getName());
        }
    }
}
kotlin
class UserExcelView : AbstractXlsxView() {

    override fun buildExcelDocument(model: Map<String, Any>, workbook: Workbook, 
                                   request: HttpServletRequest, 
                                   response: HttpServletResponse) {
        
        val sheet = workbook.createSheet("用户数据")
        val header = sheet.createRow(0)
        header.createCell(0).setCellValue("ID")
        header.createCell(1).setCellValue("用户名")

        val users = model["users"] as List<User>
        users.forEachIndexed { index, user ->
            val row = sheet.createRow(index + 1)
            row.createCell(0).setCellValue(user.id.toDouble())
            row.createCell(1).setCellValue(user.name)
        }
    }
}

控制器用法

在控制器中,你只需要返回该视图的实例即可。

java
@GetMapping("/download/users/excel")
public ModelAndView downloadExcel() {
    List<User> users = userService.findAll();
    return new ModelAndView(new UserExcelView(), "users", users);
}

补充教学

1. 为什么使用 AbstractView 而不是直接操作 Response

虽然你可以直接在控制器方法中通过 HttpServletResponse.getOutputStream() 写入数据,但继承 AbstractView 有以下好处:

  • 关注点分离:控制器只负责业务数据,视图只负责转换格式。
  • 内容协商:可以轻松集成到 Spring 的内容协商机制中,通过不同的扩展名(.pdf / .xlsx)分发到不同的视图。
  • 复用性:一个视图类可以被多个控制器复用。

2. 响应头设置

通常,下载文件时需要设置 Content-Dispositionattachment 才能弹出保存对话框。 在视图类中,你可以通过覆盖 renderMergedOutputModel 或直接在核心方法中使用 response 对象:

java
response.setHeader("Content-Disposition", "attachment; filename=\"report.xlsx\"");

3. POI 与 内存管理

在生成超大规模 Excel(如 10 万行以上)时,务必使用 AbstractXlsxStreamingView。它对应 POI 的 SXSSF 实现,会将临时行刷入磁盘,避免 OutOfMemoryError

Based on Spring Framework.