SpringBoot中使用JasperReports 报表引擎 --- 介绍、原理与使用实践
一、JasperReports 是什么
1.1 定位
JasperReports 是 Java 生态中最流行的开源报表引擎,专门用于生成格式化的文档 (PDF、Excel、Word、HTML、CSV 等)。它的核心价值是将数据 和模板分离,通过模板定义报表样式,运行时填充数据后生成最终文档。
1.2 类比理解
Word 邮件合并:
模板(.docx)+ 数据(Excel联系人) → 批量生成信件
JasperReports:
模板(.jrxml/.jasper)+ 数据(数据库/Java对象) → 批量生成 PDF/Excel/HTML
1.3 典型应用场景
| 场景 |
示例 |
| 发货单打印 |
选中订单 → 生成 PDF 发货单(含条码/表格/签章) |
| 财务报表 |
月度销售汇总 → 导出 Excel |
| 物流面单 |
快递单/运单 → 生成固定格式 PDF 批量打印 |
| 库存盘点表 |
仓库商品清单 → 导出打印 |
| 对账单 |
供应商对账 → 生成 PDF 发送邮件 |
二、核心概念
2.1 关键术语
| 概念 |
说明 |
文件格式 |
| JRXML |
报表模板源文件,XML 格式,定义报表布局和样式 |
.jrxml |
| Jasper |
编译后的模板文件(二进制),运行时直接加载 |
.jasper |
| JasperPrint |
填充数据后的内存报表对象,可导出为各种格式 |
内存对象 |
| DataSource |
数据源,为报表提供数据(JDBC/Java集合/JSON等) |
- |
| Parameter |
报表参数,从外部传入的变量(如标题、日期、Logo等) |
- |
| Field |
数据字段,对应数据源中每条记录的列 |
- |
| Band |
报表区域/带区(页眉/列头/明细/页脚/汇总等) |
- |
2.2 报表结构
┌─────────────────────────────────────────┐
│ Title Band(标题区) │ ← 整个报表只出现一次
├─────────────────────────────────────────┤
│ Page Header(页眉) │ ← 每页顶部
├─────────────────────────────────────────┤
│ Column Header(列标题) │ ← 表格列名
├─────────────────────────────────────────┤
│ Detail Band(明细区) │ ← 每条数据重复一次
│ ┌────┬──────┬────┬──────┬─────┐ │
│ │序号│ 商品名 │数量│ 单价 │ 金额 │ │
│ ├────┼──────┼────┼──────┼─────┤ │
│ │ 1 │冰箱 │ 2 │3999 │7998 │ │
│ │ 2 │洗衣机 │ 1 │2999 │2999 │ │
│ │... │... │... │... │... │ │
│ └────┴──────┴────┴──────┴─────┘ │
├─────────────────────────────────────────┤
│ Column Footer(列汇总) │ ← 合计行
├─────────────────────────────────────────┤
│ Page Footer(页脚) │ ← 每页底部(页码等)
├─────────────────────────────────────────┤
│ Summary(总汇总) │ ← 整个报表最后
└─────────────────────────────────────────┘
三、工作流程
3.1 完整生命周期
设计阶段(开发时):
Jaspersoft Studio 设计模板 → 保存为 .jrxml 文件
│
▼
编译模板:JasperCompileManager.compileReport() → .jasper 文件
│
▼(部署到项目 resources 目录)
运行阶段(运行时):
加载 .jasper 模板
│
▼
填充数据:JasperFillManager.fillReport(模板, 参数, 数据源) → JasperPrint
│
▼
导出文档:JasperExportManager.exportReportToPdf(jasperPrint) → PDF/Excel/HTML
│
▼
返回给前端下载/预览/打印
3.2 运行时数据流
Controller 接收请求(如:打印发货单)
│
├─→ Service 查询数据库获取发货单数据
│ └── List<DeliveryOrderDto> 数据集
│
├─→ 组装参数 Map(标题、公司名、打印日期等)
│
├─→ 创建数据源 JRBeanCollectionDataSource(数据集)
│
├─→ 加载模板 .jasper(从 classpath 或数据库)
│
├─→ JasperFillManager.fillReport(模板, 参数, 数据源)
│ └── → JasperPrint(内存中的完整报表)
│
├─→ 导出为目标格式
│ ├── PDF:JasperExportManager.exportReportToPdfStream()
│ ├── Excel:JRXlsxExporter
│ ├── Word:JRDocxExporter
│ └── HTML:HtmlExporter
│
└─→ 写入 HttpServletResponse 输出流
└── 前端收到文件下载/预览
四、涉及的技术知识点
4.1 模板设计
| 知识点 |
说明 |
| Jaspersoft Studio |
Eclipse 插件形式的可视化模板设计器(拖拽式) |
| JRXML 语法 |
XML 格式的模板描述语言 |
| 表达式语言 |
$F{fieldName}(字段)、$P{paramName}(参数)、$V{varName}(变量) |
| 子报表(Subreport) |
报表嵌套,主报表中嵌入子报表 |
| 条件样式 |
根据数据值动态改变颜色/字体/可见性 |
| 条码/二维码 |
内置 Barcode4J 支持各种条码格式 |
| 图表 |
内置 JFreeChart 支持柱状图/折线图/饼图 |
4.2 数据源类型
| 数据源 |
类 |
场景 |
| Java Bean 集合 |
JRBeanCollectionDataSource |
最常用,传入 List |
| JDBC 直连 |
JRResultSetDataSource |
直接执行 SQL |
| 空数据源 |
JREmptyDataSource |
只有参数没有明细数据 |
| Map 集合 |
JRMapCollectionDataSource |
List 数据 |
| JSON |
JsonDataSource |
JSON 字符串/文件 |
4.3 导出格式
| 格式 |
导出器类 |
用途 |
| PDF |
JasperExportManager |
打印、归档 |
| Excel (xlsx) |
JRXlsxExporter |
数据分析 |
| Word (docx) |
JRDocxExporter |
文档编辑 |
| HTML |
HtmlExporter |
在线预览 |
| CSV |
JRCsvExporter |
数据交换 |
| 图片 (PNG) |
JRGraphics2DExporter |
缩略图 |
4.4 设计模式
| 模式 |
应用 |
| 模板方法 |
编译 → 填充 → 导出的固定流程 |
| 策略模式 |
不同 Exporter 实现不同格式导出 |
| 建造者模式 |
ExporterInput/OutputItem 的构建 |
| 工厂模式 |
JasperCompileManager/JasperFillManager 工厂方法 |
五、通用示例代码
5.1 pom.xml 依赖
<dependencies>
<!-- JasperReports 核心 -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.20.0</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-fonts</artifactId>
<version>6.20.0</version>
</dependency>
<!-- Excel 导出 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
</dependencies>
5.2 JRXML 模板示例(发货单)
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports
http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
name="delivery_order" pageWidth="595" pageHeight="842"
columnWidth="555" leftMargin="20" rightMargin="20"
topMargin="20" bottomMargin="20">
<!-- 参数定义 -->
<parameter name="companyName" class="java.lang.String"/>
<parameter name="deliveryCode" class="java.lang.String"/>
<parameter name="printDate" class="java.lang.String"/>
<parameter name="customerName" class="java.lang.String"/>
<parameter name="address" class="java.lang.String"/>
<!-- 字段定义(对应 Java Bean 属性) -->
<field name="productCode" class="java.lang.String"/>
<field name="productName" class="java.lang.String"/>
<field name="quantity" class="java.lang.Integer"/>
<field name="price" class="java.math.BigDecimal"/>
<!-- 变量定义(自动计算) -->
<variable name="totalAmount" class="java.math.BigDecimal" calculation="Sum">
<variableExpression>
<![CDATA[$F{price}.multiply(new java.math.BigDecimal($F{quantity}))]]>
</variableExpression>
</variable>
<!-- 标题区 -->
<title>
<band height="80">
<staticText>
<reportElement x="0" y="0" width="555" height="30"/>
<textElement textAlignment="Center">
<font size="18" isBold="true" fontName="华文宋体"/>
</textElement>
<text><![CDATA[发 货 单]]></text>
</staticText>
<textField>
<reportElement x="0" y="40" width="200" height="20"/>
<textFieldExpression><![CDATA["单号:" + $P{deliveryCode}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="355" y="40" width="200" height="20"/>
<textFieldExpression><![CDATA["日期:" + $P{printDate}]]></textFieldExpression>
</textField>
</band>
</title>
<!-- 列标题 -->
<columnHeader>
<band height="25">
<staticText>
<reportElement x="0" y="0" width="100" height="25" mode="Opaque" backcolor="#CCCCCC"/>
<text><![CDATA[商品编码]]></text>
</staticText>
<staticText>
<reportElement x="100" y="0" width="200" height="25" mode="Opaque" backcolor="#CCCCCC"/>
<text><![CDATA[商品名称]]></text>
</staticText>
<staticText>
<reportElement x="300" y="0" width="80" height="25" mode="Opaque" backcolor="#CCCCCC"/>
<text><![CDATA[数量]]></text>
</staticText>
<staticText>
<reportElement x="380" y="0" width="80" height="25" mode="Opaque" backcolor="#CCCCCC"/>
<text><![CDATA[单价]]></text>
</staticText>
</band>
</columnHeader>
<!-- 明细区(每条数据重复) -->
<detail>
<band height="20">
<textField>
<reportElement x="0" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$F{productCode}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="100" y="0" width="200" height="20"/>
<textFieldExpression><![CDATA[$F{productName}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="300" y="0" width="80" height="20"/>
<textFieldExpression><![CDATA[$F{quantity}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="380" y="0" width="80" height="20"/>
<textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
</textField>
</band>
</detail>
<!-- 汇总区 -->
<summary>
<band height="30">
<textField>
<reportElement x="300" y="5" width="180" height="20"/>
<textElement textAlignment="Right">
<font isBold="true"/>
</textElement>
<textFieldExpression><![CDATA["合计金额:" + $V{totalAmount}]]></textFieldExpression>
</textField>
</band>
</summary>
</jasperReport>
5.3 JasperUtil 工具类封装
package com.example.utils;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.export.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JasperReports 报表工具类.
* 封装编译、填充、导出的完整流程.
*/
public class JasperUtil {
private static final Logger log = LoggerFactory.getLogger(JasperUtil.class);
/**
* 导出类型枚举.
*/
public enum DocType {
PDF, EXCEL, WORD, HTML
}
/**
* 生成报表并写入 HTTP 响应(文件下载).
*
* @param templatePath 模板路径(classpath 下的 .jasper 文件)
* @param params 报表参数
* @param dataList 数据集合(对应模板中的 Field)
* @param fileName 下载文件名
* @param docType 导出格式
* @param response HTTP 响应
*/
public static <T> void exportToResponse(
String templatePath,
Map<String, Object> params,
List<T> dataList,
String fileName,
DocType docType,
HttpServletResponse response) {
try {
byte[] bytes = generateReport(templatePath, params, dataList, docType);
// 设置响应头
String contentType;
String extension;
switch (docType) {
case PDF:
contentType = "application/pdf";
extension = ".pdf";
break;
case EXCEL:
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
extension = ".xlsx";
break;
case WORD:
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
extension = ".docx";
break;
default:
contentType = "text/html";
extension = ".html";
}
response.setContentType(contentType);
response.setHeader("Content-Disposition",
"attachment; filename=" + java.net.URLEncoder.encode(fileName + extension, "UTF-8"));
response.setContentLength(bytes.length);
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
} catch (Exception e) {
log.error("报表导出失败", e);
throw new RuntimeException("报表导出失败", e);
}
}
/**
* 生成报表字节数组.
*/
public static <T> byte[] generateReport(
String templatePath,
Map<String, Object> params,
List<T> dataList,
DocType docType) throws Exception {
// 1. 加载编译好的模板
InputStream templateStream = JasperUtil.class.getClassLoader()
.getResourceAsStream(templatePath);
JasperReport jasperReport = (JasperReport) JRLoader.loadObject(templateStream);
// 2. 创建数据源
JRDataSource dataSource;
if (dataList != null && !dataList.isEmpty()) {
dataSource = new JRBeanCollectionDataSource(dataList);
} else {
dataSource = new JREmptyDataSource();
}
// 3. 填充数据 → 生成 JasperPrint
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, params, dataSource);
// 4. 导出为目标格式
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
switch (docType) {
case PDF:
JasperExportManager.exportReportToPdfStream(jasperPrint, outputStream);
break;
case EXCEL:
exportToExcel(jasperPrint, outputStream);
break;
case WORD:
exportToWord(jasperPrint, outputStream);
break;
default:
JasperExportManager.exportReportToHtmlFile(jasperPrint, outputStream.toString());
}
return outputStream.toByteArray();
}
private static void exportToExcel(JasperPrint jasperPrint, ByteArrayOutputStream out) throws Exception {
JRXlsxExporter exporter = new JRXlsxExporter();
exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
SimpleXlsxReportConfiguration config = new SimpleXlsxReportConfiguration();
config.setOnePagePerSheet(false);
config.setDetectCellType(true);
exporter.setConfiguration(config);
exporter.exportReport();
}
private static void exportToWord(JasperPrint jasperPrint, ByteArrayOutputStream out) throws Exception {
JRDocxExporter exporter = new JRDocxExporter();
exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
exporter.exportReport();
}
}
5.4 业务代码使用示例
@RestController
public class DeliveryPrintController {
@Resource
private DeliveryService deliveryService;
/**
* 打印发货单(导出PDF).
*/
@GetMapping("/api/delivery/print")
public void printDeliveryOrder(
@RequestParam Integer deliveryId,
HttpServletResponse response) {
// 1. 查询发货单数据
DeliveryOrderDto order = deliveryService.getDeliveryOrder(deliveryId);
List<DeliveryItemDto> items = deliveryService.getDeliveryItems(deliveryId);
// 2. 组装报表参数
Map<String, Object> params = new HashMap<>();
params.put("companyName", "xxx科技");
params.put("deliveryCode", order.getDeliveryCode());
params.put("printDate", DateUtil.formatStandardDate(new Date()));
params.put("customerName", order.getCustomerName());
params.put("address", order.getShipToAddress());
// 3. 导出 PDF
JasperUtil.exportToResponse(
"print/delivery_order.jasper", // classpath 下的模板
params,
items, // 明细数据
"发货单_" + order.getDeliveryCode(),
JasperUtil.DocType.PDF,
response);
}
/**
* 批量打印(多个发货单合并为一个PDF).
*/
@PostMapping("/api/delivery/batch-print")
public void batchPrint(
@RequestBody List<Integer> deliveryIds,
HttpServletResponse response) {
List<JasperPrint> prints = new ArrayList<>();
for (Integer id : deliveryIds) {
DeliveryOrderDto order = deliveryService.getDeliveryOrder(id);
List<DeliveryItemDto> items = deliveryService.getDeliveryItems(id);
Map<String, Object> params = new HashMap<>();
params.put("deliveryCode", order.getDeliveryCode());
// ... 其他参数
// 生成每个发货单的 JasperPrint
InputStream template = getClass().getClassLoader()
.getResourceAsStream("print/delivery_order.jasper");
JasperReport report = (JasperReport) JRLoader.loadObject(template);
JasperPrint print = JasperFillManager.fillReport(report, params,
new JRBeanCollectionDataSource(items));
prints.add(print);
}
// 合并导出为一个 PDF
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=batch_delivery.pdf");
JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(SimpleExporterInput.getInstance(prints));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(response.getOutputStream()));
exporter.exportReport();
}
}
5.5 模板 DTO 示例
/**
* 发货单明细DTO(字段名需与JRXML中的Field名称一致).
*/
@Data
public class DeliveryItemDto {
private String productCode; // 对应 $F{productCode}
private String productName; // 对应 $F{productName}
private Integer quantity; // 对应 $F{quantity}
private BigDecimal price; // 对应 $F{price}
}
六、开发流程
6.1 模板设计(使用 Jaspersoft Studio)
1. 下载安装 Jaspersoft Studio(免费,基于 Eclipse)
2. 新建 Jasper Report → 选择模板尺寸(A4/自定义)
3. 拖拽组件到 Band 中:
- Static Text:固定文本
- Text Field:动态字段 $F{xxx}
- Image:图片/Logo
- Barcode:条码
- Line/Rectangle:线条/边框
4. 定义 Parameters(外部传入的参数)
5. 定义 Fields(对应数据源的字段)
6. 预览效果 → 保存为 .jrxml
7. 编译为 .jasper → 放到项目 resources/print/ 目录
6.2 项目中的文件组织
src/main/resources/
├── print/
│ ├── delivery_order.jrxml ← 模板源文件(用于修改)
│ ├── delivery_order.jasper ← 编译后模板(运行时加载)
│ ├── outbound_order.jasper ← 出库单模板
│ └── stock_report.jasper ← 库存报表模板
├── font/
│ └── simsun.ttf ← 中文字体文件
└── jasperreports.properties ← JasperReports 配置
七、关键设计总结
| 设计要点 |
实现方式 |
收益 |
| 数据与模板分离 |
.jasper 模板 + List 数据 |
修改样式不改代码,修改逻辑不改模板 |
| 预编译模板 |
.jrxml → .jasper(开发时编译) |
运行时直接加载,无需编译开销 |
| 多格式输出 |
同一模板导出 PDF/Excel/Word/HTML |
一次设计,多种输出 |
| 批量合并 |
多个 JasperPrint 合并为一个 PDF |
批量打印只需下载一个文件 |
| 中文支持 |
嵌入字体文件到项目中 |
避免服务器字体缺失导致中文乱码 |
| 参数化 |
$P{paramName} 动态传入 |
同一模板适配不同场景(换标题/Logo/签章) |
| 子报表嵌套 |
Subreport 组件 |
主从报表(如订单主信息 + 商品明细) |
| 表达式计算 |
$V{variable} + calculation=Sum |
自动计算合计/平均/计数 |