Spring Boot iText
最后修改于 2020 年 7 月 13 日
在本教程中,我们将介绍如何使用 iText 和 Spring Boot 创建 PDF 报告。数据将从 H2 内存数据库中的表中加载。
iText 是一个用于在 Java 中创建和操作 PDF 文件的开源库。
Spring 是一个用于开发 Java 企业应用程序的 Java 应用程序框架。它还有助于集成各种企业组件。Spring Boot 使创建具有最少设置要求的 Spring 驱动的生产级应用程序和服务变得容易。
H2 是一个完全用 Java 实现的开源关系数据库管理系统。它可以嵌入到 Java 应用程序中,也可以以客户端-服务器模式运行。它占用的空间小,易于部署和安装。它包含一个基于浏览器的控制台应用程序,用于查看和编辑数据库表。
JdbcTemplate 是一个 Spring 库,它帮助程序员创建与关系数据库和 JDBC 协同工作的应用程序。它负责许多乏味且容易出错的低级细节,例如处理事务、清理资源以及正确处理异常。JdbcTemplate 包含在 Spring 的 spring-jdbc 模块中。
应用程序
以下 Spring Boot 应用程序从数据库表中加载数据,并使用 iText 库从此生成 PDF 报告。该应用程序在嵌入式 Tomcat 服务器上运行。
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── zetcode
│ │ ├── Application.java
│ │ ├── bean
│ │ │ └── Car.java
│ │ ├── conf
│ │ │ └── AppConfig.java
│ │ ├── controller
│ │ │ └── MyController.java
│ │ ├── service
│ │ │ ├── CarService.java
│ │ │ └── ICarService.java
│ │ └── view
│ │ ├── AbstractPdfView.java
│ │ └── MyPdfView.java
│ └── resources
│ ├── application.yml
│ ├── data-h2.sql
│ ├── schema-h2.sql
│ └── static
│ └── index.html
└── test
└── java
这是项目结构。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SpringBootItext</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>4.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Maven pom.xml 文件包含 iText 库、H2 驱动程序和 Spring 框架的依赖项。
package com.zetcode.bean;
public class Car {
private Long id;
private String name;
private int price;
public Car() {}
public Car(Long id, String name, int price) {
this.id = id;
this.name = name;
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" + "id=" + id + ", name=" + name + ", price=" + price + '}';
}
}
这是 Car bean 类。它包含项目 ID、名称和价格。
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
driverClassName: org.h2.Driver
spring:
datasource:
platform: h2
h2:
console:
enabled: true
path: /console/
application.yml 是主要的 Spring Boot 配置文件。它包含数据源和 MVC 设置。我们选择了 H2 作为数据库系统。数据库在内存中运行。我们启用了基于浏览器的控制台应用程序。
url: jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
数据库名称为 testdb,它是在内存中创建的。(Spring Boot 在 Maven POM 文件中检测到 H2 时会自动创建一个内存数据库,但我们也会展示如何显式创建。)
spring:
datasource:
platform: h2
platform 值用于 SQL 初始化脚本:schema-${platform}.sql 和 data-${platform}.sql。
h2:
console:
enabled: true
path: /console/
H2 Web 控制台应用程序已启用;它可以通过 localhost:8080/console/ 路径访问。
CREATE TABLE Cars(ID BIGINT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(30), PRICE INT);
此 SQL 脚本创建 Cars 表。
INSERT INTO Cars(Name, Price) VALUES('Audi', 52642);
INSERT INTO Cars(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO Cars(Name, Price) VALUES('Skoda', 9000);
INSERT INTO Cars(Name, Price) VALUES('Volvo', 29000);
INSERT INTO Cars(Name, Price) VALUES('Bentley', 350000);
INSERT INTO Cars(Name, Price) VALUES('Citroen', 21000);
INSERT INTO Cars(Name, Price) VALUES('Hummer', 41400);
INSERT INTO Cars(Name, Price) VALUES('Volkswagen', 21600);
此脚本用数据填充表。这两个脚本都位于类路径的根目录。
package com.zetcode.conf;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class AppConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
}
AppConfig 是一个 Java 配置类。它从 application.yml 配置文件创建数据源 bean。
package com.zetcode.view;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.view.AbstractView;
public abstract class AbstractPdfView extends AbstractView {
public AbstractPdfView() {
initView();
}
private void initView() {
setContentType("application/pdf");
}
@Override
protected boolean generatesDownloadContent() {
return true;
}
@Override
protected final void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
ByteArrayOutputStream baos = createTemporaryOutputStream();
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, baos);
prepareWriter(model, writer, request);
buildPdfMetadata(model, document, request);
document.open();
buildPdfDocument(model, document, writer, request, response);
document.close();
writeToResponse(response, baos);
}
protected void prepareWriter(Map<String, Object> model, PdfWriter writer,
HttpServletRequest request) throws DocumentException {
writer.setViewerPreferences(getViewerPreferences());
}
protected int getViewerPreferences() {
return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage;
}
protected void buildPdfMetadata(Map<String, Object> model, Document document,
HttpServletRequest request) {
}
protected abstract void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
Spring 的 AbstractPdfView 基于旧的 iText 库。因此,我们需要创建自己的抽象类,它基本上是一个带有更新导入的副本。
package com.zetcode.view;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.zetcode.bean.Car;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyPdfView extends AbstractPdfView {
@Override
protected void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception {
List<Car> cars = (List<Car>) model.get("cars");
PdfPTable table = new PdfPTable(3);
table.setWidthPercentage(60);
table.setWidths(new int[] {1, 3, 3});
Font headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD);
PdfPCell hcell;
hcell = new PdfPCell(new Phrase("Id", headFont));
hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(hcell);
hcell = new PdfPCell(new Phrase("Name", headFont));
hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(hcell);
hcell = new PdfPCell(new Phrase("Price", headFont));
hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(hcell);
for (Car car : cars) {
PdfPCell cell;
cell = new PdfPCell(new Phrase(car.getId().toString()));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell = new PdfPCell(new Phrase(car.getName()));
cell.setPaddingLeft(5);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_LEFT);
table.addCell(cell);
cell = new PdfPCell(new Phrase(String.valueOf(car.getPrice())));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
cell.setPaddingRight(5);
table.addCell(cell);
}
document.add(table);
}
}
MyPdfView 继承自自定义的 AbstractPdfView。在 buildPdfDocument 方法中,我们构建 PDF 文件。
List<Car> cars = (List<Car>) model.get("cars");
首先,我们从模型中获取数据。
PdfPTable table = new PdfPTable(3);
我们将把数据放在一个表中;为此,我们有 PdfPTable 类。该表有三列:Id、Name 和 Price。
Font headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD);
对于表头,我们使用粗体 Helvetica 字体。
PdfPCell hcell;
hcell = new PdfPCell(new Phrase("Id", headFont));
hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(hcell);
数据放置在表单元格中,由 PdfPCell 表示。文本使用 setHorizontalAlignment 方法进行水平对齐。
document.add(table);
最后,将表插入 PDF 文档。
package com.zetcode.controller;
import com.zetcode.bean.Car;
import com.zetcode.service.ICarService;
import com.zetcode.view.MyPdfView;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
@Autowired
private ICarService carService;
@RequestMapping(path = "/report", method = RequestMethod.GET)
public ModelAndView report() {
Map<String, Object> model = new HashMap<>();
List<Car> cars = carService.findAll();
model.put("cars", cars);
return new ModelAndView(new MyPdfView(), model);
}
}
在 MyController 中,我们有一个映射。
@Autowired private ICarService carService;
我们将 CarService 对象注入到属性中。该服务对象用于从数据库检索数据。
@RequestMapping(path = "/report", method = RequestMethod.GET)
public ModelAndView report() {
Map<String, Object> model = new HashMap<>();
List<Car> cars = carService.findAll();
model.put("cars", cars);
return new ModelAndView(new MyPdfView(), model);
}
在 report 方法中,我们使用 findAll 方法查找所有汽车。我们将自定义的 MyPdfView 返回给客户端。
package com.zetcode.service;
import com.zetcode.bean.Car;
import java.util.List;
public interface ICarService {
public List<Car> findAll();
}
ICarService 提供了一个契约方法,用于从数据源获取所有汽车。
package com.zetcode.service;
import com.zetcode.bean.Car;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class CarService implements ICarService {
@Autowired
private JdbcTemplate jtm;
@Override
public List<Car> findAll() {
String sql = "SELECT * FROM Cars";
List<Car> cars = jtm.query(sql, new BeanPropertyRowMapper(Car.class));
return cars;
}
}
CarService 包含 findAll 方法的实现。我们借助 JdbcTemplate 从 Cars 表中检索所有汽车。
@Autowired private JdbcTemplate jtm;
JdbcTemplate 已注入。
String sql = "SELECT * FROM Cars";
这是要执行的 SQL。我们从 Cars 表中选择所有汽车。
List<Car> cars = jtm.query(sql, new BeanPropertyRowMapper(Car.class));
BeanPropertyRowMapper 将一行转换为指定映射目标类的新实例。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<a href="/report.html">Generate report</a>
</body>
</html>
index.html 文件包含一个用于生成 PDF 报告的链接。静态文件从预定义的目录提供;其中之一是 static,位于 src/main/resources。
package com.zetcode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
`Application` 设置了 Spring Boot 应用程序。
$ mvn spring-boot:run
我们启动 Spring Boot 应用程序。
导航到 https://:8080/ 来测试应用程序。H2 控制台应用程序可通过 https://:8080/console/ 访问。控制台应用程序的 JDBC URL 为 jdbc:h2:mem:testdb。密码为空。
在本教程中,我们使用 iText 从 H2 数据库中的表创建了 PDF 报告。该应用程序使用了 Spring Boot 框架,并在 Web 环境中运行。