ZetCode

Java Servlet 提供 PDF

最后修改于 2023 年 8 月 24 日

Java Servlet PDF 教程展示了如何从 Java Servlet 返回 PDF 数据。我们使用 iText 库来处理 PDF。

PDF

便携式文档格式 (PDF) 是一种文件格式,用于以独立于应用程序软件、硬件和操作系统的方式呈现文档。PDF 由 Adobe 发明,现已成为国际标准化组织 (ISO) 维护的一项开放标准。

Java Servlet

Servlet 是一个 Java 类,它响应特定类型的网络请求——最常见的是 HTTP 请求。Java Servlet 用于创建 Web 应用程序。它们在 Tomcat 或 Jetty 等 Servlet 容器中运行。当今的 Java Web 开发使用构建在 Servlet 之上的框架。

iText

iText 是一个用于在 Java 中创建和操作 PDF 文件的开源库。

Java Servlet PDF 应用程序

以下 Web 应用程序使用 Java Servlet 将 PDF 文件发送到客户端。它根据对象列表生成 PDF。

pom.xml
src
├── main
│  ├── java
│  │  └── com
│  │      └── zetcode
│  │          ├── bean
│  │          │  └── City.java
│  │          ├── service
│  │          │  └── CityService.java
│  │          ├── util
│  │          │  └── GeneratePdf.java
│  │          └── web
│  │              └── MyServlet.java
│  └── resources
└── test
    └── java

这是项目结构。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
    <artifactId>JavaServletPdf</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>


        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13.3</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.3</version>
            </plugin>

            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>11.0.11</version>
                <configuration>
                    <webApp>
                        <contextPath>/app</contextPath>
                    </webApp>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

这是 Maven POM 文件。我们有两个构件:用于 Servlet 的 jakarta.servlet-api 和用于 Java PDF 生成的 itextpdfmaven-war-plugin 负责收集 Web 应用程序的所有构件依赖项、类和资源,并将它们打包成 Web 应用程序归档 (WAR)。jetty-maven-plugin 是一个辅助插件,用于轻松测试和集成 Jetty 与 Maven。

<configuration>
    <webApp>
        <contextPath>/app</contextPath>
    </webApp>
</configuration>

在 Jetty Maven 插件中,我们将上下文路径设置为 /app

com/zetcode/bean/City.java
package com.zetcode.bean;

public class City {

    private Long id;
    private String name;
    private int population;

    public City() {
    }

    public City(Long id, String name, int population) {
        this.id = id;
        this.name = name;
        this.population = population;
    }

    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 getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }

    @Override
    public String toString() {
        return "City{" + "id=" + id + ", name=" + name + 
                ", population=" + population + '}';
    }
}

这是 City bean。它有三个属性:idnamepopulation

com/zetcode/web/MyServlet.java
package com.zetcode.web;

import com.zetcode.bean.City;
import com.zetcode.service.CityService;
import com.zetcode.util.GeneratePdf;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "MyServlet", urlPatterns = {"/pdf"})
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        response.setContentType("application/pdf;charset=UTF-8");

        response.addHeader("Content-Disposition", "inline; filename=" + "cities.pdf");
        ServletOutputStream out = response.getOutputStream();

        List<City> cities = CityService.getCities();

        ByteArrayOutputStream baos = GeneratePdf.getPdfFile(cities);
        baos.writeTo(out);
    }
}

这是 MyServlet Servlet。它从服务类检索数据,根据数据生成 PDF 文件,并将 PDF 文件返回给客户端。

response.setContentType("application/pdf;charset=UTF-8");

我们将响应对象的 content type 设置为 application/pdf

response.addHeader("Content-Disposition", "inline; filename=" + "cities.pdf");

Content-Disposition 响应头指示内容是期望在浏览器中“内联”显示,即作为网页或网页的一部分,还是作为“附件”下载并本地保存。可选的 filename 指令指定传输的文件名。

ServletOutputStream out = response.getOutputStream();

我们从响应对象中获取 ServletOutputStream

List<City> cities = CityService.getCities();

CityService,我们获取城市列表。

ByteArrayOutputStream baos = GeneratePdf.getPdfFile(cities);
baos.writeTo(out);

我们根据数据生成 PDF 文件,并将返回的 ByteArrayOutputStream 写入 ServletOutputStream

com/zetcode/service/CityService.java
package com.zetcode.service;

import com.zetcode.bean.City;

import java.util.List;

public class CityService {

    public static List<City> getCities() {

        var cities = List.of(
                new City(1L, "Bratislava", 432000),
                new City(2L, "Budapest", 1759000),
                new City(3L, "Prague", 1280000),
                new City(4L, "Warsaw", 1748000),
                new City(5L, "Los Angeles", 3971000),
                new City(6L, "New York", 8550000),
                new City(7L, "Edinburgh", 464000),
                new City(8L, "Berlin", 3671000));

        return cities;
    }
}

CityServicegetCities 方法返回一个城市对象列表。

com/zetcode/util/GeneratePdf.java
package com.zetcode.util;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
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.City;

import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GeneratePdf {

    public static ByteArrayOutputStream getPdfFile(List<City> cities) {

        var document = new Document();
        var bout = new ByteArrayOutputStream();

        try {

            var table = new PdfPTable(3);
            table.setWidthPercentage(60);
            table.setWidths(new int[]{1, 3, 3});

            Font headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD);

            var 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("Population", headFont));
            hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(hcell);

            for (City city : cities) {

                var cell = new PdfPCell(new Phrase(city.getId().toString()));
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                table.addCell(cell);

                cell = new PdfPCell(new Phrase(city.getName()));
                cell.setPaddingLeft(5);
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_LEFT);
                table.addCell(cell);

                cell = new PdfPCell(new Phrase(String.valueOf(city.getPopulation())));
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
                cell.setPaddingRight(5);
                table.addCell(cell);
            }

            PdfWriter.getInstance(document, bout);
            document.open();
            document.add(table);

            document.close();

        } catch (DocumentException ex) {

            Logger.getLogger(GeneratePdf.class.getName()).log(Level.SEVERE, null, ex);
        }

        return bout;
    }
}

GeneratePdf 根据提供的数据创建 PDF 文件。

var bout = new ByteArrayOutputStream();

数据将被写入 ByteArrayOutputStreamByteArrayOutputStream 实现了一个输出流,其中的数据被写入字节数组。

var table = new PdfPTable(3);

我们将数据放入表中;为此,我们有 PdfPTable 类。该表有三列:Id、Name 和 Population。

Font headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD);

我们为表头使用粗体 Helvetica 字体。

var hcell = new PdfPCell(new Phrase("Id", headFont));
hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(hcell);

数据放置在表单元格中,由 PdfPCell 表示。setHorizontalAlignment 方法水平对齐文本。

PdfWriter.getInstance(document, bout);

使用 PdfWriter,文档被写入 ByteArrayOutputStream

document.open();
document.add(table);

表被插入到 PDF 文档中。

document.close();

为了将数据写入 ByteArrayOutputStream,必须关闭文档。

return bout; 

最后,数据作为 ByteArrayOutputStream 返回。

$ mvn jetty:run

启动 Jetty 服务器并导航到 localhost:8080/app/pdf

在本文中,我们已从 Java Servlet 发送了 PDF 数据。

列出 所有 Java Servlet 教程。