简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français

站内搜索

搜索

活动公告

11-02 12:46
10-23 09:32
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31
10-23 09:28
通知:签到时间调整为每日4:00(东八区)
10-23 09:26

JSP PDF生成实例教程从基础到进阶全面解析JSP页面动态生成PDF文件的技术实现方法与常见问题解决方案

3万

主题

424

科技点

3万

积分

大区版主

木柜子打湿

积分
31917

三倍冰淇淋无人之境【一阶】财Doro小樱(小丑装)立华奏以外的星空【二阶】⑨的冰沙

发表于 2025-9-26 17:30:00 | 显示全部楼层 |阅读模式 [标记阅至此楼]

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 引言

在当今的信息时代,PDF(Portable Document Format)作为一种跨平台的文件格式,被广泛应用于文档交换、报告生成、电子书出版等领域。在Java Web开发中,特别是在JSP(JavaServer Pages)环境中,动态生成PDF文件是一项常见且重要的需求。无论是生成报表、电子发票、合同文档还是其他类型的业务文档,JSP生成PDF技术都发挥着关键作用。

本教程将从基础到进阶,全面解析在JSP页面中动态生成PDF文件的技术实现方法,并提供详细的实例代码和常见问题的解决方案,帮助开发者快速掌握这一技能。

2. 基础知识

2.1 PDF生成的基本原理

在JSP中生成PDF文件,本质上是通过Java程序将数据按照特定的格式和布局输出为PDF格式。这个过程通常涉及以下几个步骤:

1. 数据准备:从数据库或其他数据源获取需要展示的数据
2. 模板设计:设计PDF的布局和样式
3. 内容生成:将数据填充到模板中
4. PDF转换:将内容转换为PDF格式
5. 输出响应:将生成的PDF文件发送给客户端

2.2 JSP环境配置

在开始之前,我们需要确保JSP环境已经正确配置。以下是基本的环境要求:

1. JDK 1.6或更高版本
2. Tomcat或其他Java EE容器
3. 相关的PDF生成库(将在下一节详细介绍)

为了演示,我们假设你已经有一个可以正常运行的JSP项目。如果还没有,可以按照以下步骤创建一个简单的JSP项目:

1. 在Eclipse或IntelliJ IDEA中创建一个新的Dynamic Web Project
2. 配置Tomcat服务器
3. 创建一个简单的JSP页面,例如index.jsp
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>JSP PDF Generation</title>
  7. </head>
  8. <body>
  9.     <h1>Welcome to JSP PDF Generation Tutorial</h1>
  10. </body>
  11. </html>
复制代码

3. 常用JSP PDF生成库介绍

在Java生态系统中,有多个优秀的库可以用于PDF生成。下面介绍几个常用的库及其特点:

3.1 iText

iText是最流行的Java PDF库之一,提供了丰富的功能来创建和操作PDF文件。它支持文本、表格、图片、水印等多种元素,并且可以处理复杂的布局需求。

特点:

• 功能全面,支持几乎所有的PDF操作
• 社区活跃,文档丰富
• 有免费版本和商业版本
• 对中文支持良好(需要额外配置)

Maven依赖:
  1. <dependency>
  2.     <groupId>com.itextpdf</groupId>
  3.     <artifactId>itextpdf</artifactId>
  4.     <version>5.5.13.3</version>
  5. </dependency>
  6. <!-- 如果需要支持中文,还需要添加以下依赖 -->
  7. <dependency>
  8.     <groupId>com.itextpdf</groupId>
  9.     <artifactId>itext-asian</artifactId>
  10.     <version>5.2.0</version>
  11. </dependency>
复制代码

3.2 Apache FOP

Apache FOP(Formatting Objects Processor)是一个XSL-FO处理器,可以将XSL-FO转换为PDF。它适合于需要将XML数据转换为PDF的场景。

特点:

• 基于XSL-FO标准,适合复杂的文档布局
• 与XML技术栈集成良好
• 支持多种输出格式(PDF、PS、PCL等)
• 学习曲线较陡峭

Maven依赖:
  1. <dependency>
  2.     <groupId>org.apache.xmlgraphics</groupId>
  3.     <artifactId>fop</artifactId>
  4.     <version>2.6</version>
  5. </dependency>
复制代码

3.3 Flying Saucer

Flying Saucer是一个基于iText的HTML/CSS渲染器,可以将HTML和CSS直接渲染为PDF。这对于熟悉Web开发的开发者来说非常友好。

特点:

• 使用HTML和CSS定义PDF样式,学习成本低
• 支持大部分CSS2.1和一些CSS3特性
• 适合将现有网页转换为PDF
• 对复杂的CSS布局支持有限

Maven依赖:
  1. <dependency>
  2.     <groupId>org.xhtmlrenderer</groupId>
  3.     <artifactId>flying-saucer-pdf</artifactId>
  4.     <version>9.1.22</version>
  5. </dependency>
复制代码

3.4 JasperReports

JasperReports是一个强大的报表引擎,可以生成各种格式的报表,包括PDF。它提供了可视化的报表设计工具,适合复杂的报表需求。

特点:

• 提供可视化的报表设计工具
• 支持多种数据源
• 适合复杂的报表需求
• 学习曲线较陡峭

Maven依赖:
  1. <dependency>
  2.     <groupId>net.sf.jasperreports</groupId>
  3.     <artifactId>jasperreports</artifactId>
  4.     <version>6.20.0</version>
  5. </dependency>
复制代码

在本教程中,我们将主要使用iText和Flying Saucer这两个库来演示PDF生成的实例,因为它们相对简单易用,且能满足大部分需求。

4. 基础实例:简单的PDF生成

让我们从最简单的例子开始,使用iText库在JSP中生成一个包含简单文本的PDF文件。

4.1 使用iText生成简单PDF

首先,创建一个JSP页面,命名为simplePDF.jsp:
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <%@ page import="com.itextpdf.text.Document" %>
  3. <%@ page import="com.itextpdf.text.DocumentException" %>
  4. <%@ page import="com.itextpdf.text.Paragraph" %>
  5. <%@ page import="com.itextpdf.text.pdf.PdfWriter" %>
  6. <%@ page import="java.io.ByteArrayOutputStream" %>
  7. <%@ page import="java.io.IOException" %>
  8. <%
  9.     // 设置响应内容类型为PDF
  10.     response.setContentType("application/pdf");
  11.    
  12.     // 创建文档对象
  13.     Document document = new Document();
  14.    
  15.     try {
  16.         // 创建字节输出流
  17.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  18.         
  19.         // 获取PDF写入器
  20.         PdfWriter.getInstance(document, baos);
  21.         
  22.         // 打开文档
  23.         document.open();
  24.         
  25.         // 添加内容
  26.         document.add(new Paragraph("Hello, World!"));
  27.         document.add(new Paragraph("这是一个简单的PDF生成示例。"));
  28.         
  29.         // 关闭文档
  30.         document.close();
  31.         
  32.         // 设置响应头
  33.         response.setHeader("Content-Disposition", "inline; filename=simple.pdf");
  34.         response.setContentLength(baos.size());
  35.         
  36.         // 将PDF写入响应输出流
  37.         baos.writeTo(response.getOutputStream());
  38.         
  39.         // 刷新输出流
  40.         response.getOutputStream().flush();
  41.     } catch (DocumentException | IOException e) {
  42.         e.printStackTrace();
  43.         throw new ServletException("生成PDF时出错", e);
  44.     }
  45. %>
复制代码

这个例子中,我们做了以下几件事:

1. 导入了必要的iText类
2. 设置响应内容类型为application/pdf,告诉浏览器这是一个PDF文件
3. 创建了一个Document对象,这是iText中表示PDF文档的主要类
4. 创建了一个ByteArrayOutputStream来存储生成的PDF内容
5. 使用PdfWriter将文档内容写入输出流
6. 打开文档并添加了一些简单的段落
7. 设置响应头,指定文件名和内容长度
8. 将PDF内容写入响应输出流,发送给客户端

当你访问这个JSP页面时,浏览器会显示一个包含”Hello, World!“和中文文本的PDF文件。

4.2 使用Flying Saucer生成简单PDF

现在,让我们使用Flying Saucer库来实现同样的功能。创建一个新的JSP页面,命名为simplePDFWithFlyingSaucer.jsp:
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <%@ page import="org.xhtmlrenderer.pdf.ITextRenderer" %>
  3. <%@ page import="java.io.ByteArrayOutputStream" %>
  4. <%@ page import="java.io.IOException" %>
  5. <%
  6.     // 设置响应内容类型为PDF
  7.     response.setContentType("application/pdf");
  8.    
  9.     try {
  10.         // HTML内容
  11.         String html = "<html><head><style>body { font-family: Arial; }</style></head><body>" +
  12.                      "<h1>Hello, World!</h1>" +
  13.                      "<p>这是一个使用Flying Saucer生成的PDF示例。</p>" +
  14.                      "</body></html>";
  15.         
  16.         // 创建字节输出流
  17.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  18.         
  19.         // 创建ITextRenderer
  20.         ITextRenderer renderer = new ITextRenderer();
  21.         
  22.         // 设置HTML内容
  23.         renderer.setDocumentFromString(html);
  24.         
  25.         // 布局
  26.         renderer.layout();
  27.         
  28.         // 创建PDF
  29.         renderer.createPDF(baos);
  30.         
  31.         // 关闭
  32.         renderer.finishPDF();
  33.         
  34.         // 设置响应头
  35.         response.setHeader("Content-Disposition", "inline; filename=simple_flying_saucer.pdf");
  36.         response.setContentLength(baos.size());
  37.         
  38.         // 将PDF写入响应输出流
  39.         baos.writeTo(response.getOutputStream());
  40.         
  41.         // 刷新输出流
  42.         response.getOutputStream().flush();
  43.     } catch (Exception e) {
  44.         e.printStackTrace();
  45.         throw new ServletException("生成PDF时出错", e);
  46.     }
  47. %>
复制代码

在这个例子中,我们:

1. 导入了Flying Saucer的ITextRenderer类
2. 创建了一个包含HTML内容的字符串
3. 使用ITextRenderer将HTML渲染为PDF
4. 将生成的PDF写入响应输出流

与iText相比,Flying Saucer允许我们使用HTML和CSS来定义PDF的内容和样式,这对于熟悉Web开发的开发者来说更加直观。

5. 进阶实例:复杂的PDF生成

现在我们已经了解了基本的PDF生成方法,让我们来看一些更复杂的例子,包括表格、图片、水印等元素。

5.1 使用iText生成包含表格和图片的PDF

创建一个新的JSP页面,命名为advancedPDF.jsp:
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <%@ page import="com.itextpdf.text.*" %>
  3. <%@ page import="com.itextpdf.text.pdf.*" %>
  4. <%@ page import="java.io.ByteArrayOutputStream" %>
  5. <%@ page import="java.io.IOException" %>
  6. <%@ page import="java.net.URL" %>
  7. <%
  8.     // 设置响应内容类型为PDF
  9.     response.setContentType("application/pdf");
  10.    
  11.     // 创建文档对象,设置页面大小和边距
  12.     Document document = new Document(PageSize.A4, 50, 50, 50, 50);
  13.    
  14.     try {
  15.         // 创建字节输出流
  16.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  17.         
  18.         // 获取PDF写入器
  19.         PdfWriter writer = PdfWriter.getInstance(document, baos);
  20.         
  21.         // 打开文档
  22.         document.open();
  23.         
  24.         // 添加标题
  25.         Font titleFont = new Font(Font.FontFamily.HELVETICA, 18, Font.BOLD);
  26.         Paragraph title = new Paragraph("高级PDF生成示例", titleFont);
  27.         title.setAlignment(Element.ALIGN_CENTER);
  28.         document.add(title);
  29.         
  30.         // 添加空行
  31.         document.add(Chunk.NEWLINE);
  32.         
  33.         // 添加段落
  34.         Font normalFont = new Font(Font.FontFamily.HELVETICA, 12);
  35.         Paragraph paragraph = new Paragraph("这个示例展示了如何在PDF中添加表格、图片和水印等元素。", normalFont);
  36.         document.add(paragraph);
  37.         
  38.         // 添加空行
  39.         document.add(Chunk.NEWLINE);
  40.         
  41.         // 创建表格
  42.         PdfPTable table = new PdfPTable(3); // 3列
  43.         table.setWidthPercentage(100); // 表格宽度为100%
  44.         
  45.         // 添加表头
  46.         PdfPCell cell = new PdfPCell(new Phrase("ID", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)));
  47.         cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  48.         table.addCell(cell);
  49.         
  50.         cell = new PdfPCell(new Phrase("名称", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)));
  51.         cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  52.         table.addCell(cell);
  53.         
  54.         cell = new PdfPCell(new Phrase("价格", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)));
  55.         cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  56.         table.addCell(cell);
  57.         
  58.         // 添加表格数据
  59.         table.addCell("1");
  60.         table.addCell("产品A");
  61.         table.addCell("$100.00");
  62.         
  63.         table.addCell("2");
  64.         table.addCell("产品B");
  65.         table.addCell("$200.00");
  66.         
  67.         table.addCell("3");
  68.         table.addCell("产品C");
  69.         table.addCell("$300.00");
  70.         
  71.         // 将表格添加到文档
  72.         document.add(table);
  73.         
  74.         // 添加空行
  75.         document.add(Chunk.NEWLINE);
  76.         
  77.         // 添加图片
  78.         try {
  79.             // 从URL加载图片
  80.             Image image = Image.getInstance(new URL("https://picsum.photos/seed/pdfexample/200/100.jpg"));
  81.             image.setAlignment(Element.ALIGN_CENTER);
  82.             document.add(image);
  83.         } catch (Exception e) {
  84.             document.add(new Paragraph("无法加载图片: " + e.getMessage()));
  85.         }
  86.         
  87.         // 添加空行
  88.         document.add(Chunk.NEWLINE);
  89.         
  90.         // 添加水印
  91.         PdfContentByte canvas = writer.getDirectContentUnder();
  92.         canvas.beginText();
  93.         canvas.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED), 48);
  94.         canvas.setColorFill(BaseColor.LIGHT_GRAY);
  95.         canvas.showTextAligned(Element.ALIGN_CENTER, "WATERMARK", document.getPageSize().getWidth() / 2, document.getPageSize().getHeight() / 2, 45);
  96.         canvas.endText();
  97.         
  98.         // 添加页码
  99.         Phrase footer = new Phrase("Page ", new Font(Font.FontFamily.HELVETICA, 10));
  100.         HeaderFooter pageFooter = new HeaderFooter(footer, true);
  101.         pageFooter.setAlignment(Element.ALIGN_CENTER);
  102.         pageFooter.setBorder(Rectangle.NO_BORDER);
  103.         document.setFooter(pageFooter);
  104.         
  105.         // 关闭文档
  106.         document.close();
  107.         
  108.         // 设置响应头
  109.         response.setHeader("Content-Disposition", "inline; filename=advanced.pdf");
  110.         response.setContentLength(baos.size());
  111.         
  112.         // 将PDF写入响应输出流
  113.         baos.writeTo(response.getOutputStream());
  114.         
  115.         // 刷新输出流
  116.         response.getOutputStream().flush();
  117.     } catch (DocumentException | IOException e) {
  118.         e.printStackTrace();
  119.         throw new ServletException("生成PDF时出错", e);
  120.     }
  121. %>
复制代码

这个例子展示了如何使用iText创建一个包含以下元素的PDF:

1. 标题:使用较大的粗体字居中显示
2. 段落:包含普通文本
3. 表格:包含表头和数据行,表头有背景色
4. 图片:从URL加载并居中显示
5. 水印:在页面中央以45度角显示的灰色文字
6. 页脚:包含页码

5.2 使用Flying Saucer生成包含复杂样式的PDF

现在,让我们使用Flying Saucer创建一个包含复杂样式的PDF。创建一个新的JSP页面,命名为advancedPDFWithFlyingSaucer.jsp:
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <%@ page import="org.xhtmlrenderer.pdf.ITextRenderer" %>
  3. <%@ page import="java.io.ByteArrayOutputStream" %>
  4. <%@ page import="java.io.IOException" %>
  5. <%
  6.     // 设置响应内容类型为PDF
  7.     response.setContentType("application/pdf");
  8.    
  9.     try {
  10.         // HTML内容,包含复杂的CSS样式
  11.         String html = "<!DOCTYPE html>" +
  12.                      "<html>" +
  13.                      "<head>" +
  14.                      "<meta charset="UTF-8">" +
  15.                      "<title>高级PDF示例</title>" +
  16.                      "<style>" +
  17.                      "body {" +
  18.                      "    font-family: Arial, sans-serif;" +
  19.                      "    margin: 0;" +
  20.                      "    padding: 20px;" +
  21.                      "    color: #333;" +
  22.                      "}" +
  23.                      "h1 {" +
  24.                      "    color: #2c3e50;" +
  25.                      "    text-align: center;" +
  26.                      "    border-bottom: 2px solid #3498db;" +
  27.                      "    padding-bottom: 10px;" +
  28.                      "}" +
  29.                      "table {" +
  30.                      "    width: 100%;" +
  31.                      "    border-collapse: collapse;" +
  32.                      "    margin: 20px 0;" +
  33.                      "}" +
  34.                      "th, td {" +
  35.                      "    border: 1px solid #ddd;" +
  36.                      "    padding: 8px;" +
  37.                      "    text-align: left;" +
  38.                      "}" +
  39.                      "th {" +
  40.                      "    background-color: #f2f2f2;" +
  41.                      "    font-weight: bold;" +
  42.                      "}" +
  43.                      "tr:nth-child(even) {" +
  44.                      "    background-color: #f9f9f9;" +
  45.                      "}" +
  46.                      "img {" +
  47.                      "    display: block;" +
  48.                      "    margin: 20px auto;" +
  49.                      "    max-width: 100%;" +
  50.                      "}" +
  51.                      ".watermark {" +
  52.                      "    position: fixed;" +
  53.                      "    top: 50%;" +
  54.                      "    left: 50%;" +
  55.                      "    transform: translate(-50%, -50%) rotate(-45deg);" +
  56.                      "    font-size: 48px;" +
  57.                      "    color: rgba(200, 200, 200, 0.5);" +
  58.                      "    z-index: -1;" +
  59.                      "    pointer-events: none;" +
  60.                      "}" +
  61.                      "@page {" +
  62.                      "    size: A4;" +
  63.                      "    margin: 1cm;" +
  64.                      "    @bottom-center {" +
  65.                      "        content: "Page " counter(page) " of " counter(pages);" +
  66.                      "        font-size: 10pt;" +
  67.                      "    }" +
  68.                      "}" +
  69.                      "</style>" +
  70.                      "</head>" +
  71.                      "<body>" +
  72.                      "<div class="watermark">WATERMARK</div>" +
  73.                      "<h1>高级PDF生成示例</h1>" +
  74.                      "<p>这个示例展示了如何使用Flying Saucer生成包含复杂样式的PDF文档。</p>" +
  75.                      "<table>" +
  76.                      "<thead>" +
  77.                      "<tr>" +
  78.                      "<th>ID</th>" +
  79.                      "<th>名称</th>" +
  80.                      "<th>价格</th>" +
  81.                      "</tr>" +
  82.                      "</thead>" +
  83.                      "<tbody>" +
  84.                      "<tr>" +
  85.                      "<td>1</td>" +
  86.                      "<td>产品A</td>" +
  87.                      "<td>$100.00</td>" +
  88.                      "</tr>" +
  89.                      "<tr>" +
  90.                      "<td>2</td>" +
  91.                      "<td>产品B</td>" +
  92.                      "<td>$200.00</td>" +
  93.                      "</tr>" +
  94.                      "<tr>" +
  95.                      "<td>3</td>" +
  96.                      "<td>产品C</td>" +
  97.                      "<td>$300.00</td>" +
  98.                      "</tr>" +
  99.                      "</tbody>" +
  100.                      "</table>" +
  101.                      "<img src="https://picsum.photos/seed/pdfexample/400/200.jpg" alt="示例图片">" +
  102.                      "<p>Flying Saucer允许我们使用HTML和CSS来定义PDF的样式,这使得我们可以利用Web开发的知识来创建精美的PDF文档。</p>" +
  103.                      "</body>" +
  104.                      "</html>";
  105.         
  106.         // 创建字节输出流
  107.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  108.         
  109.         // 创建ITextRenderer
  110.         ITextRenderer renderer = new ITextRenderer();
  111.         
  112.         // 设置HTML内容
  113.         renderer.setDocumentFromString(html);
  114.         
  115.         // 布局
  116.         renderer.layout();
  117.         
  118.         // 创建PDF
  119.         renderer.createPDF(baos);
  120.         
  121.         // 关闭
  122.         renderer.finishPDF();
  123.         
  124.         // 设置响应头
  125.         response.setHeader("Content-Disposition", "inline; filename=advanced_flying_saucer.pdf");
  126.         response.setContentLength(baos.size());
  127.         
  128.         // 将PDF写入响应输出流
  129.         baos.writeTo(response.getOutputStream());
  130.         
  131.         // 刷新输出流
  132.         response.getOutputStream().flush();
  133.     } catch (Exception e) {
  134.         e.printStackTrace();
  135.         throw new ServletException("生成PDF时出错", e);
  136.     }
  137. %>
复制代码

这个例子展示了如何使用Flying Saucer创建一个包含复杂样式的PDF:

1. 使用CSS定义了整个文档的样式,包括字体、颜色、边距等
2. 创建了一个带有边框和斑马纹的表格
3. 添加了一个居中显示的图片
4. 使用CSS的position: fixed和transform属性创建了一个水印效果
5. 使用CSS的@page规则定义了页面大小和页脚

与iText相比,Flying Saucer允许我们使用更直观的HTML和CSS来定义PDF的样式,这对于熟悉Web开发的开发者来说更加友好。

5.3 从数据库生成PDF

在实际应用中,我们经常需要从数据库获取数据并生成PDF。下面是一个从数据库获取数据并生成PDF的示例。

首先,假设我们有一个简单的产品表,包含id、name和price字段。我们使用JDBC来获取数据并生成PDF。

创建一个新的JSP页面,命名为databasePDF.jsp:
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <%@ page import="com.itextpdf.text.*" %>
  3. <%@ page import="com.itextpdf.text.pdf.*" %>
  4. <%@ page import="java.io.ByteArrayOutputStream" %>
  5. <%@ page import="java.io.IOException" %>
  6. <%@ page import="java.sql.*" %>
  7. <%
  8.     // 设置响应内容类型为PDF
  9.     response.setContentType("application/pdf");
  10.    
  11.     // 创建文档对象
  12.     Document document = new Document(PageSize.A4, 50, 50, 50, 50);
  13.    
  14.     Connection conn = null;
  15.     PreparedStatement stmt = null;
  16.     ResultSet rs = null;
  17.    
  18.     try {
  19.         // 创建字节输出流
  20.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  21.         
  22.         // 获取PDF写入器
  23.         PdfWriter writer = PdfWriter.getInstance(document, baos);
  24.         
  25.         // 打开文档
  26.         document.open();
  27.         
  28.         // 添加标题
  29.         Font titleFont = new Font(Font.FontFamily.HELVETICA, 18, Font.BOLD);
  30.         Paragraph title = new Paragraph("产品列表", titleFont);
  31.         title.setAlignment(Element.ALIGN_CENTER);
  32.         document.add(title);
  33.         
  34.         // 添加空行
  35.         document.add(Chunk.NEWLINE);
  36.         
  37.         // 创建表格
  38.         PdfPTable table = new PdfPTable(3); // 3列
  39.         table.setWidthPercentage(100); // 表格宽度为100%
  40.         
  41.         // 添加表头
  42.         PdfPCell cell = new PdfPCell(new Phrase("ID", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)));
  43.         cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  44.         table.addCell(cell);
  45.         
  46.         cell = new PdfPCell(new Phrase("名称", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)));
  47.         cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  48.         table.addCell(cell);
  49.         
  50.         cell = new PdfPCell(new Phrase("价格", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD)));
  51.         cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  52.         table.addCell(cell);
  53.         
  54.         // 数据库连接信息
  55.         String dbUrl = "jdbc:mysql://localhost:3306/your_database";
  56.         String dbUser = "username";
  57.         String dbPassword = "password";
  58.         
  59.         // 加载驱动
  60.         Class.forName("com.mysql.jdbc.Driver");
  61.         
  62.         // 建立连接
  63.         conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
  64.         
  65.         // 创建查询
  66.         String sql = "SELECT id, name, price FROM products";
  67.         stmt = conn.prepareStatement(sql);
  68.         
  69.         // 执行查询
  70.         rs = stmt.executeQuery();
  71.         
  72.         // 遍历结果集并添加到表格
  73.         while (rs.next()) {
  74.             int id = rs.getInt("id");
  75.             String name = rs.getString("name");
  76.             double price = rs.getDouble("price");
  77.             
  78.             table.addCell(String.valueOf(id));
  79.             table.addCell(name);
  80.             table.addCell("$" + String.format("%.2f", price));
  81.         }
  82.         
  83.         // 将表格添加到文档
  84.         document.add(table);
  85.         
  86.         // 添加空行
  87.         document.add(Chunk.NEWLINE);
  88.         
  89.         // 添加生成时间
  90.         Font normalFont = new Font(Font.FontFamily.HELVETICA, 10);
  91.         Paragraph generatedTime = new Paragraph("生成时间: " + new java.util.Date(), normalFont);
  92.         generatedTime.setAlignment(Element.ALIGN_RIGHT);
  93.         document.add(generatedTime);
  94.         
  95.         // 关闭文档
  96.         document.close();
  97.         
  98.         // 设置响应头
  99.         response.setHeader("Content-Disposition", "inline; filename=products.pdf");
  100.         response.setContentLength(baos.size());
  101.         
  102.         // 将PDF写入响应输出流
  103.         baos.writeTo(response.getOutputStream());
  104.         
  105.         // 刷新输出流
  106.         response.getOutputStream().flush();
  107.     } catch (DocumentException | IOException | ClassNotFoundException | SQLException e) {
  108.         e.printStackTrace();
  109.         throw new ServletException("生成PDF时出错", e);
  110.     } finally {
  111.         // 关闭数据库资源
  112.         try {
  113.             if (rs != null) rs.close();
  114.             if (stmt != null) stmt.close();
  115.             if (conn != null) conn.close();
  116.         } catch (SQLException e) {
  117.             e.printStackTrace();
  118.         }
  119.     }
  120. %>
复制代码

这个例子展示了如何:

1. 使用JDBC连接数据库并查询数据
2. 将查询结果添加到PDF表格中
3. 在PDF中添加生成时间等动态信息
4. 正确处理数据库资源的关闭

注意:在实际应用中,你应该将数据库连接信息存储在配置文件中,而不是直接写在JSP中。此外,为了提高性能和可维护性,你应该考虑使用连接池和DAO模式。

6. 常见问题及解决方案

在使用JSP生成PDF的过程中,开发者可能会遇到各种问题。下面是一些常见问题及其解决方案。

6.1 中文乱码问题

在生成包含中文的PDF时,最常见的问题就是中文显示为乱码或方框。这是因为默认情况下,iText等库不支持中文字体。

解决方案:

1. 使用支持中文的字体文件:
  1. <%@ page import="com.itextpdf.text.pdf.BaseFont" %>
  2. <%
  3.     // 创建支持中文的字体
  4.     BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
  5.     Font fontChinese = new Font(bfChinese, 12, Font.NORMAL);
  6.    
  7.     // 使用中文字体
  8.     Paragraph paragraph = new Paragraph("这是一个中文段落", fontChinese);
  9.     document.add(paragraph);
  10. %>
复制代码

1. 使用iText的亚洲字体包:
  1. <dependency>
  2.     <groupId>com.itextpdf</groupId>
  3.     <artifactId>itext-asian</artifactId>
  4.     <version>5.2.0</version>
  5. </dependency>
复制代码

然后使用BaseFont.createFont()方法加载中文字体:
  1. BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
  2. Font fontChinese = new Font(bfChinese, 12, Font.NORMAL);
复制代码

1. 对于Flying Saucer,确保HTML中指定了正确的字符集和字体:
  1. <meta charset="UTF-8">
  2. <style>
  3.     body {
  4.         font-family: "SimSun", "宋体", serif;
  5.     }
  6. </style>
复制代码

6.2 样式问题

在使用Flying Saucer时,可能会发现某些CSS样式不被支持或渲染效果与预期不符。

解决方案:

1. 检查Flying Saucer的CSS支持情况,它主要支持CSS2.1和部分CSS3特性。
2. 避免使用不支持的CSS属性,如float、position: absolute等。
3. 使用表格布局代替复杂的CSS布局。
4. 如果必须使用不支持的CSS特性,考虑使用iText或其他库。

6.3 图片问题

在PDF中添加图片时,可能会遇到图片不显示、图片过大或图片质量下降等问题。

解决方案:

1. 确保图片路径正确,可以使用绝对路径或URL。
2. 调整图片大小:
  1. Image image = Image.getInstance("path/to/image.jpg");
  2. image.scaleToFit(400, 300); // 调整图片大小,最大宽度400,最大高度300
  3. document.add(image);
复制代码

1. 控制图片质量:
  1. Image image = Image.getInstance("path/to/image.jpg");
  2. image.setCompressionLevel(9); // 设置压缩级别,0-9,9为最高压缩率
  3. document.add(image);
复制代码

1. 对于Flying Saucer,确保图片路径是相对于HTML文件的,或者使用绝对路径。

6.4 性能问题

生成大型PDF文件或在高并发环境下生成PDF时,可能会遇到性能问题。

解决方案:

1. 使用缓存:对于不常变化的内容,可以生成PDF后缓存起来,避免重复生成。
2. 优化内存使用:及时关闭Document和PdfWriter对象,释放内存。
3. 分页处理:对于大型报表,考虑分页生成或分批处理数据。
4. 使用异步生成:对于耗时较长的PDF生成任务,可以使用异步处理,避免阻塞用户请求。

6.5 安全问题

PDF生成可能涉及敏感数据,需要考虑安全性问题。

解决方案:

1. 添加密码保护:
  1. PdfWriter writer = PdfWriter.getInstance(document, baos);
  2. writer.setEncryption("userpassword".getBytes(), "ownerpassword".getBytes(),
  3.     PdfWriter.ALLOW_PRINTING, PdfWriter.ENCRYPTION_AES_128);
复制代码

1. 防止PDF注入:确保用户输入的数据经过适当的验证和转义,防止恶意内容被注入到PDF中。
2. 控制访问权限:确保只有授权用户才能访问PDF生成功能。
3. 记录日志:记录PDF生成操作,便于审计和追踪。

6.6 兼容性问题

不同的PDF阅读器可能对某些特性支持不同,导致PDF在某些阅读器中显示异常。

解决方案:

1. 遵守PDF标准:使用广泛支持的PDF特性,避免使用专有或实验性功能。
2. 测试多种阅读器:在Adobe Reader、Foxit Reader等多种PDF阅读器中测试生成的PDF。
3. 提供替代方案:对于复杂的PDF,考虑提供HTML版本作为替代。

7. 最佳实践

在JSP中生成PDF时,遵循一些最佳实践可以提高代码质量、性能和可维护性。

7.1 代码组织

1. 将PDF生成逻辑封装到单独的类中,而不是直接写在JSP中:
  1. public class PDFGenerator {
  2.     public static byte[] generateProductReport(List<Product> products) throws DocumentException, IOException {
  3.         Document document = new Document(PageSize.A4, 50, 50, 50, 50);
  4.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  5.         
  6.         PdfWriter.getInstance(document, baos);
  7.         document.open();
  8.         
  9.         // 添加内容
  10.         document.add(new Paragraph("产品报告"));
  11.         
  12.         PdfPTable table = new PdfPTable(3);
  13.         // 添加表头
  14.         table.addCell("ID");
  15.         table.addCell("名称");
  16.         table.addCell("价格");
  17.         
  18.         // 添加数据
  19.         for (Product product : products) {
  20.             table.addCell(String.valueOf(product.getId()));
  21.             table.addCell(product.getName());
  22.             table.addCell("$" + product.getPrice());
  23.         }
  24.         
  25.         document.add(table);
  26.         document.close();
  27.         
  28.         return baos.toByteArray();
  29.     }
  30. }
复制代码

1. 在Servlet中处理PDF生成请求,而不是直接在JSP中:
  1. @WebServlet("/productReport")
  2. public class ProductReportServlet extends HttpServlet {
  3.     protected void doGet(HttpServletRequest request, HttpServletResponse response)
  4.             throws ServletException, IOException {
  5.         
  6.         try {
  7.             // 获取产品数据
  8.             List<Product> products = ProductDAO.getAllProducts();
  9.             
  10.             // 生成PDF
  11.             byte[] pdfData = PDFGenerator.generateProductReport(products);
  12.             
  13.             // 设置响应
  14.             response.setContentType("application/pdf");
  15.             response.setHeader("Content-Disposition", "inline; filename=product_report.pdf");
  16.             response.setContentLength(pdfData.length);
  17.             
  18.             // 写入响应
  19.             response.getOutputStream().write(pdfData);
  20.             response.getOutputStream().flush();
  21.         } catch (DocumentException e) {
  22.             throw new ServletException("生成PDF时出错", e);
  23.         }
  24.     }
  25. }
复制代码

1. 使用MVC模式,将数据获取、业务逻辑和视图生成分离。

7.2 异常处理

1. 使用try-catch块捕获可能的异常,并提供有意义的错误信息:
  1. try {
  2.     // PDF生成代码
  3. } catch (DocumentException e) {
  4.     logger.error("生成PDF时出错", e);
  5.     throw new ServletException("无法生成PDF文档", e);
  6. } catch (IOException e) {
  7.     logger.error("写入PDF时出错", e);
  8.     throw new ServletException("无法写入PDF文档", e);
  9. }
复制代码

1. 对于用户输入的数据,进行验证和清理:
  1. String title = request.getParameter("title");
  2. if (title == null || title.trim().isEmpty()) {
  3.     title = "默认标题";
  4. }
  5. // 清理HTML标签,防止XSS攻击
  6. title = title.replaceAll("<[^>]*>", "");
复制代码

7.3 性能优化

1. 使用对象池重用PDF生成对象:
  1. public class PDFWriterPool {
  2.     private static final int MAX_POOL_SIZE = 10;
  3.     private static final List<PdfWriter> pool = new ArrayList<>(MAX_POOL_SIZE);
  4.    
  5.     public static PdfWriter getPdfWriter(Document document, OutputStream os) throws DocumentException {
  6.         synchronized (pool) {
  7.             if (!pool.isEmpty()) {
  8.                 PdfWriter writer = pool.remove(pool.size() - 1);
  9.                 writer.setPdfWriter(document, os);
  10.                 return writer;
  11.             }
  12.         }
  13.         return PdfWriter.getInstance(document, os);
  14.     }
  15.    
  16.     public static void returnPdfWriter(PdfWriter writer) {
  17.         synchronized (pool) {
  18.             if (pool.size() < MAX_POOL_SIZE) {
  19.                 writer.close();
  20.                 pool.add(writer);
  21.             }
  22.         }
  23.     }
  24. }
复制代码

1. 使用缓存减少重复生成:
  1. public class PDFCache {
  2.     private static final Map<String, byte[]> cache = new ConcurrentHashMap<>();
  3.     private static final long CACHE_EXPIRY_TIME = 60 * 60 * 1000; // 1小时
  4.    
  5.     public static byte[] getCachedPDF(String key) {
  6.         CacheEntry entry = (CacheEntry) cache.get(key);
  7.         if (entry != null && !entry.isExpired()) {
  8.             return entry.getData();
  9.         }
  10.         return null;
  11.     }
  12.    
  13.     public static void cachePDF(String key, byte[] data) {
  14.         cache.put(key, new CacheEntry(data));
  15.     }
  16.    
  17.     private static class CacheEntry {
  18.         private final byte[] data;
  19.         private final long timestamp;
  20.         
  21.         public CacheEntry(byte[] data) {
  22.             this.data = data;
  23.             this.timestamp = System.currentTimeMillis();
  24.         }
  25.         
  26.         public boolean isExpired() {
  27.             return System.currentTimeMillis() - timestamp > CACHE_EXPIRY_TIME;
  28.         }
  29.         
  30.         public byte[] getData() {
  31.             return data;
  32.         }
  33.     }
  34. }
复制代码

1. 使用异步处理提高响应速度:
  1. @WebServlet("/asyncPDFReport")
  2. public class AsyncPDFReportServlet extends HttpServlet {
  3.     protected void doGet(HttpServletRequest request, HttpServletResponse response)
  4.             throws ServletException, IOException {
  5.         
  6.         // 启动异步处理
  7.         AsyncContext asyncContext = request.startAsync();
  8.         
  9.         // 在新线程中生成PDF
  10.         ExecutorService executor = (ExecutorService) request.getServletContext()
  11.                 .getAttribute("executorService");
  12.         
  13.         executor.submit(() -> {
  14.             try {
  15.                 // 获取产品数据
  16.                 List<Product> products = ProductDAO.getAllProducts();
  17.                
  18.                 // 生成PDF
  19.                 byte[] pdfData = PDFGenerator.generateProductReport(products);
  20.                
  21.                 // 设置响应
  22.                 HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
  23.                 asyncResponse.setContentType("application/pdf");
  24.                 asyncResponse.setHeader("Content-Disposition", "inline; filename=product_report.pdf");
  25.                 asyncResponse.setContentLength(pdfData.length);
  26.                
  27.                 // 写入响应
  28.                 asyncResponse.getOutputStream().write(pdfData);
  29.                 asyncResponse.getOutputStream().flush();
  30.             } catch (Exception e) {
  31.                 try {
  32.                     HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
  33.                     asyncResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "生成PDF时出错");
  34.                 } catch (IOException ioException) {
  35.                     // 记录错误
  36.                 }
  37.             } finally {
  38.                 asyncContext.complete();
  39.             }
  40.         });
  41.     }
  42. }
复制代码

7.4 安全性考虑

1. 添加PDF密码保护:
  1. public class SecurePDFGenerator {
  2.     public static byte[] generateSecurePDF(List<Product> products, String userPassword, String ownerPassword)
  3.             throws DocumentException, IOException {
  4.         
  5.         Document document = new Document(PageSize.A4, 50, 50, 50, 50);
  6.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  7.         
  8.         PdfWriter writer = PdfWriter.getInstance(document, baos);
  9.         // 设置用户密码和所有者密码
  10.         writer.setEncryption(
  11.             userPassword.getBytes(),
  12.             ownerPassword.getBytes(),
  13.             PdfWriter.ALLOW_PRINTING,
  14.             PdfWriter.ENCRYPTION_AES_128
  15.         );
  16.         
  17.         document.open();
  18.         
  19.         // 添加内容
  20.         document.add(new Paragraph("安全产品报告"));
  21.         
  22.         PdfPTable table = new PdfPTable(3);
  23.         // 添加表头和数据...
  24.         
  25.         document.add(table);
  26.         document.close();
  27.         
  28.         return baos.toByteArray();
  29.     }
  30. }
复制代码

1. 添加数字签名:
  1. public class SignedPDFGenerator {
  2.     public static byte[] generateSignedPDF(List<Product> products, PrivateKey pk, Certificate[] chain)
  3.             throws DocumentException, IOException, GeneralSecurityException {
  4.         
  5.         Document document = new Document(PageSize.A4, 50, 50, 50, 50);
  6.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  7.         
  8.         PdfWriter writer = PdfWriter.getInstance(document, baos);
  9.         document.open();
  10.         
  11.         // 添加内容
  12.         document.add(new Paragraph("签名产品报告"));
  13.         
  14.         PdfPTable table = new PdfPTable(3);
  15.         // 添加表头和数据...
  16.         
  17.         document.add(table);
  18.         
  19.         // 添加签名
  20.         PdfSignatureAppearance appearance = writer.getSignatureAppearance();
  21.         appearance.setReason("文档认证");
  22.         appearance.setLocation("服务器");
  23.         appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "signature");
  24.         
  25.         ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, "BC");
  26.         MakeSignature.signDetached(appearance, signature, chain, null, null, null, 0, CryptoStandard.CMS);
  27.         
  28.         document.close();
  29.         
  30.         return baos.toByteArray();
  31.     }
  32. }
复制代码

1. 防止PDF注入攻击:
  1. public class SafePDFGenerator {
  2.     public static byte[] generateSafePDF(String userInput) throws DocumentException, IOException {
  3.         // 清理用户输入,防止PDF注入
  4.         String safeInput = sanitizeUserInput(userInput);
  5.         
  6.         Document document = new Document();
  7.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  8.         
  9.         PdfWriter.getInstance(document, baos);
  10.         document.open();
  11.         
  12.         // 使用清理后的输入
  13.         document.add(new Paragraph(safeInput));
  14.         
  15.         document.close();
  16.         
  17.         return baos.toByteArray();
  18.     }
  19.    
  20.     private static String sanitizeUserInput(String input) {
  21.         // 移除可能有害的字符
  22.         return input.replaceAll("[\\p{Cntrl}]", "");
  23.     }
  24. }
复制代码

8. 总结与展望

本教程从基础到进阶,全面解析了在JSP页面中动态生成PDF文件的技术实现方法。我们介绍了常用的PDF生成库,包括iText、Flying Saucer、Apache FOP和JasperReports,并通过详细的实例展示了如何使用这些库生成简单的和复杂的PDF文档。我们还讨论了常见的问题及其解决方案,以及一些最佳实践。

随着技术的发展,PDF生成技术也在不断进步。未来,我们可以期待以下发展趋势:

1. 更好的CSS支持:随着CSS标准的不断发展,PDF生成库对CSS的支持将更加完善,使得Web开发者可以更轻松地创建复杂的PDF布局。
2. 更高的性能:随着多核处理器的普及,PDF生成库将更好地利用多线程和并行处理,提高生成速度。
3. 更丰富的交互功能:未来的PDF生成技术可能会支持更多的交互功能,如表单、动画、视频等,使PDF文档更加生动。
4. 更好的移动设备支持:随着移动设备的普及,PDF生成技术将更好地适应移动设备的屏幕尺寸和交互方式。
5. 云服务集成:将PDF生成功能作为云服务提供,使开发者可以更轻松地集成PDF生成功能,而无需担心服务器配置和维护。

更好的CSS支持:随着CSS标准的不断发展,PDF生成库对CSS的支持将更加完善,使得Web开发者可以更轻松地创建复杂的PDF布局。

更高的性能:随着多核处理器的普及,PDF生成库将更好地利用多线程和并行处理,提高生成速度。

更丰富的交互功能:未来的PDF生成技术可能会支持更多的交互功能,如表单、动画、视频等,使PDF文档更加生动。

更好的移动设备支持:随着移动设备的普及,PDF生成技术将更好地适应移动设备的屏幕尺寸和交互方式。

云服务集成:将PDF生成功能作为云服务提供,使开发者可以更轻松地集成PDF生成功能,而无需担心服务器配置和维护。

无论技术如何发展,掌握PDF生成的基本原理和常用技术对于Java Web开发者来说都是一项重要的技能。希望本教程能够帮助你快速掌握JSP PDF生成技术,并在实际项目中应用这些知识。

最后,记住学习是一个持续的过程。不断探索新的技术和方法,参与社区讨论,分享你的经验和知识,这将使你成为一名更优秀的开发者。祝你学习愉快!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

频道订阅

频道订阅

加入社群

加入社群

联系我们|TG频道|RSS

Powered by Pixtech

© 2025 Pixtech Team.