Spring Boot 集成 JasperReports
JasperReports 官网:https://community.jaspersoft.com/.
导入依赖 pom.xml
:
<!-- JasperReports 核心库 -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>7.0.1</version>
<exclusions>
<exclusion>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- JasperReports PDF 扩展库 -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-pdf</artifactId>
<version>7.0.1</version> <!-- 与 JasperReports 核心版本保持一致 -->
</dependency>
<!-- JasperReports 字体扩展库,用于解决中文不能识别的问题 -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-fonts</artifactId>
<version>7.0.1</version>
</dependency>
基础设施
接口:
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import org.springframework.http.ResponseEntity;
import java.sql.SQLException;
import java.util.Map;
public interface JasperService {
/**
* 输出 PDF 二进制流响应
* @param fileName 输出文件名
* @param parameters 渲染参数
* @param dataSource 自定义渲染数据源,传递 null 表示使用数据库的数据源
* @return 二进制流
* @throws JRException Jasper 操作异常
* @throws SQLException 数据库异常
*/
ResponseEntity<byte[]> PDF(String fileName, Map<String, Object> parameters,
JRDataSource dataSource) throws JRException, SQLException;
}
实现:
import cn.hutool.core.lang.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.util.JRLoader;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.sql.DataSource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@RequiredArgsConstructor
public class JasperServiceImpl implements JasperService {
// 注入项目默认数据源,需在配置文件 application.properties 中进行配置
private final DataSource systemDataSource;
@Override
public ResponseEntity<byte[]> PDF(String fileName, Map<String, Object> parameters, JRDataSource dataSource) throws JRException, SQLException {
JasperPrint jasperPrint = FillData(fileName, parameters, dataSource);
// 生成 PDF 二进制字节
byte[] bytes = JasperExportManager.exportReportToPdf(jasperPrint);
// 创建一个新的 HTTP 头集合对象,用于设置响应头信息
HttpHeaders header = new HttpHeaders();
// 指定响应内容类型为 PDF(application/pdf)
header.setContentType(MediaType.APPLICATION_PDF);
// 设置缓存控制头
// maxAge():即 max-age=600,表示资源可以被缓存 10 分钟
// mustRevalidate():即 must-revalidate,表示资源过期后必须重新验证
header.setCacheControl(CacheControl
.maxAge(10, TimeUnit.MINUTES)
.mustRevalidate());
// 设置内容处置
header.setContentDisposition(
// inline:指示浏览器应尝试在页面内显示PDF(而不是下载)
ContentDisposition.builder("inline")
// 设置导出文件名
.filename("report_" + UUID.fastUUID() + ".pdf")
.build()
);
// 构建响应实体,创建 HTTP 200 OK 响应
return ResponseEntity.ok()
// 添加上面设置的所有头信息
.headers(header)
// 设置响应体为 PDF 文件的字节数组
.body(bytes);
}
private JasperPrint FillData(String fileName, Map<String, Object> parameters, JRDataSource dataSource) throws JRException, SQLException {
// 获取 Jasper 模板文件
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
if (ObjectUtils.isEmpty(inputStream)) log.error("Error loading file {}", fileName);
// 合并请求参数和系统参数
JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputStream);
JasperPrint jasperPrint;
if (!ObjectUtils.isEmpty(dataSource))
// 使用自定义的数据源进行数据渲染
jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);
else
// 使用项目配置数据源,.jrxml 中可以根据 SQL 从数据库中拉取数据并渲染
jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, systemDataSource.getConnection());
return jasperPrint;
}
}
配置文件
在 resources
目录下创建字体目录 fonts
。
fonts/fonts.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 注册字体,根元素 <fontFamilies> 包含所有字体定义 -->
<fontFamilies>
<!-- <fontFamily> 定义了一个名为"华文宋体"的字体族,.jrxml 中引用的字体名称 -->
<fontFamily name="华文宋体">
<!-- 字体文件路径,需放在 /resources 目录下 -->
<!-- 指定了四种字体样式的文件路径(都是同一个 TTF 文件) -->
<!-- 虽然华文宋体可能没有真正的粗体或斜体变体,但这里都指向同一文件 -->
<normal>fonts/STSONG.TTF</normal>
<bold>fonts/STSONG.TTF</bold>
<italic>fonts/STSONG.TTF</italic>
<boldItalic>fonts/STSONG.TTF</boldItalic>
<!-- pdfEncoding:指定PDF编码为 Identity-H (Unicode 水平书写) -->
<pdfEncoding>Identity-H</pdfEncoding>
<!-- pdfEmbedded:设置为 true 表示将字体嵌入 PDF 文件中 -->
<pdfEmbedded>true</pdfEmbedded>
<!-- 定义 HTML 和 XHTML 导出时的字体回退链 -->
<exportFonts>
<!-- 如果"华文宋体"不可用,会依次尝试 Arial、Helvetica 和 sans-serif 字体 -->
<export key="net.sf.jasperreports.html">'华文宋体', Arial, Helvetica, sans-serif</export>
<export key="net.sf.jasperreports.xhtml">'华文宋体', Arial, Helvetica, sans-serif</export>
</exportFonts>
</fontFamily>
</fontFamilies>
注册字体
创建配置文件,必须定义为 jasperreports.properties
:
# 强制 JasperReports 忽略缺失的字体(避免 JRFontNotFoundException)
# JasperReports 遇到未安装的字体时不会报错,而是使用默认字体替换
net.sf.jasperreports.awt.ignore.missing.font=true
创建配置文件,必须定义为 jasperreports_extension.properties
:
# 注册字体扩展工厂
net.sf.jasperreports.extension.registry.factory.simple.font.families=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
# 指定字体定义文件路径(需在 classpath 下),fonts/fonts.xml:字体注册文件
net.sf.jasperreports.extension.simple.font.families.lobstertwo=fonts/fonts.xml
注意:以上两个配置文件不能合成一个配置文件!!
创建 PDF 模板
xxx.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="lq_item"
language="java"
pageWidth="842"
pageHeight="595"
orientation="Landscape"
columnWidth="802"
leftMargin="20"
rightMargin="20"
topMargin="20"
bottomMargin="20"
uuid="d231d38f-0c4c-456f-8a9a-6418352f4484"
whenNoDataType="AllSectionsNoDetail"
isIgnorePagination="false">
<!-- 若是需要显示中文,则必须配置字体文件!! -->
<!-- fontName:设置字体样式,若系统中找不到对应字体会报错!! -->
<!-- 这里使用自行导入的字体,字体引用名称为:华文宋体 -->
<style name="Title" fontName="华文宋体" fontSize="30" isBold="true" hTextAlign="Center" vTextAlign="Middle"/>
<style name="SubTitle" fontName="华文宋体" fontSize="16" hTextAlign="Center" vTextAlign="Middle"/>
<style name="ColumnHeader" fontName="华文宋体" fontSize="14" isBold="true" hTextAlign="Center" vTextAlign="Middle"/>
<style name="Detail" fontName="华文宋体" fontSize="14" hTextAlign="Center" vTextAlign="Middle"/>
<!-- 设置普通参数 -->
<parameter name="title" class="java.lang.String"/>
<parameter name="time" class="java.lang.String"/>
<!-- 设置列表参数,需要循环渲染的参数 -->
<field name="id" class="java.lang.Integer"/>
<field name="account" class="java.lang.String"/>
<field name="name" class="java.lang.String"/>
<field name="memo" class="java.lang.String"/>
<field name="ghInfo" class="java.lang.String"/>
<background>
<band splitType="Stretch"/>
</background>
<title>
<band height="100">
<textField>
<reportElement x="0" y="10" width="802" height="40" style="Title"/>
<!-- $P{xxx}:对应上面设置的 <parameter> 普通参数 -->
<textFieldExpression><![CDATA[$P{title}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="60" width="802" height="30" style="SubTitle"/>
<textFieldExpression><![CDATA["时间:" + $P{time}]]></textFieldExpression>
</textField>
</band>
</title>
<pageHeader>
<band height="0"/>
</pageHeader>
<columnHeader>
<band height="40">
<staticText>
<reportElement x="17" y="0" width="121" height="40" style="ColumnHeader"/>
<box>
<topPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<text><![CDATA[序号]]></text>
</staticText>
<staticText>
<reportElement x="138" y="0" width="121" height="40" style="ColumnHeader"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<text><![CDATA[账号/联系电话]]></text>
</staticText>
<staticText>
<reportElement x="259" y="0" width="121" height="40" style="ColumnHeader"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<text><![CDATA[姓名]]></text>
</staticText>
<staticText>
<reportElement x="380" y="0" width="151" height="40" style="ColumnHeader"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<text><![CDATA[人员备注]]></text>
</staticText>
<staticText>
<reportElement x="531" y="0" width="240" height="40" style="ColumnHeader"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<text><![CDATA[衣物信息]]></text>
</staticText>
</band>
</columnHeader>
<detail>
<band height="30">
<textField>
<reportElement x="17" y="0" width="121" height="30" style="Detail"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<!-- $F{xxx}:对应上面设置的 <field> 列表参数 -->
<textFieldExpression><![CDATA[$F{id}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="138" y="0" width="121" height="30" style="Detail"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textFieldExpression><![CDATA[$F{account}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="259" y="0" width="121" height="30" style="Detail"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textFieldExpression><![CDATA[$F{name}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="380" y="0" width="151" height="30" style="Detail"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textFieldExpression><![CDATA[$F{memo}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="531" y="0" width="240" height="30" style="Detail"/>
<box>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textFieldExpression><![CDATA[$F{ghInfo}]]></textFieldExpression>
</textField>
</band>
</detail>
<columnFooter>
<band height="0"/>
</columnFooter>
<pageFooter>
<band height="0">
</band>
</pageFooter>
<summary>
<band height="0"/>
</summary>
</jasperReport>
将 .jrxml
文件复制到 Jaspersoft Studio 中并编译成 .jasper
文件,然后将 .jasper
文件拖入到 resources
目录中。
Resources 目录层级结构

渲染数据并导出
import lombok.RequiredArgsConstructor;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequiredArgsConstructor
@RequestMapping("/jasper")
public class JasperController {
private final JasperService jasperService;
@GetMapping("/pdf")
public ResponseEntity<byte[]> PDF() throws JRException, SQLException {
// 模板文件相对位置,需要放在 resources 目录下!!
String template = "jasper/xxx.jasper";
// 填充普通参数 $P{xxx}
Map<String, Object> parameters = new HashMap<>();
parameters.put("title", "Test Table");
parameters.put("time", "2999-10-12");
// 填充元素/列表参数 $F{xxx}
List<Map<String, Object>> data = Arrays.asList(
new HashMap<>() {{
put("id", 1);
put("account", "001");
put("name", "name1");
put("memo", "memo1");
put("ghInfo", "info1");
}},
new HashMap<>() {{
put("id", 2);
put("account", "002");
put("name", "name2");
put("memo", "memo2");
put("ghInfo", "info2");
}}
);
// 将自定义数据源转换成 Jasper 支持的类型
JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(data);
return jasperService.PDF(template, parameters, dataSource);
}
}