导读:本期聚焦于小伙伴创作的《如何使用Java生成PDF报表?iText库添加水印与表格布局实战》,敬请观看详情。直接操作PDF底层结构来绘制报表往往费时费力,iText通过高层API屏蔽了坐标计算与字体编码细节。本文围绕水印叠加与表格布局两个高频需求,说明如何利用PdfWriter事件机制在每页注入半透明文本,以及如何用Table对象控制列宽、边框与单元格合并。对比手动定位文本,事件监听器能统一处理页眉页脚,避免内容与背景相互覆盖。示例基于iText 7,涵盖依赖引入、文档初始化、水印实现类编写与复杂表头构建,帮助后端服务快速产出带品牌标识的统计数据文件。

在企业级后台系统中,经常需要把查询结果导出成PDF格式的统计报表。Java生态里,iText是最常用的开源库之一,它提供了从文本、图片到表格、水印的完整排版能力。借助iText,开发者不必关心PDF规范的复杂语法,就能生成符合打印要求的中文报表。

如何使用Java生成PDF报表?iText库添加水印与表格布局实战

一、环境准备与基础文档创建

在正式写报表逻辑前,先要把iText 7的核心依赖引入项目。Maven工程里添加下面两个坐标即可,其中layout模块包含了表格、段落等高级元素,kernel模块负责底层PDF写入。

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>kernel</artifactId>
    <version>7.2.5</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>layout</artifactId>
    <version>7.2.5</version>
</dependency>

创建PDF文档的标准流程是:先用PdfWriter绑定输出流,再用PdfDocument包装,最后套上Document对象来方便添加元素。下面代码演示了最简化的初始化过程,实际项目中输出流通常指向HTTP响应或服务器文件。

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;

import java.io.FileOutputStream;

public class PdfDemo {
    public static void main(String[] args) throws Exception {
        // 绑定文件输出流
        PdfWriter writer = new PdfWriter(new FileOutputStream("report.pdf"));
        // 创建PDF文档对象
        PdfDocument pdf = new PdfDocument(writer);
        // 用布局模块文档便于添加高级元素
        Document document = new Document(pdf);
        document.add(new com.itextpdf.layout.element.Paragraph("测试报表"));
        document.close();
    }
}

这种基础写法能跑通,但缺少水印和表格。后面两节会在它之上扩展。值得注意的是,Document关闭时会自动刷新写入器,忘记close会导致文件损坏,因此在Web场景建议放在try-with-resources里。

二、用事件机制添加全局水印

iText 7允许给PdfWriter添加事件处理器,在每页结束渲染时回调。我们自定义一个继承PdfDocumentEvent类的监听器,在onAcceptedEvent方法里拿到当前页的PdfCanvas,然后绘制半透明文本作为水印。这种方式比手动在每页加段落更统一,也不会挤占正文布局。

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;

public class WatermarkHandler implements IEventHandler {
    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdfDoc = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        Rectangle pageSize = page.getPageSize();
        // 在页面中心绘制水印
        PdfCanvas canvas = new PdfCanvas(page.newContentStreamAfter(),
                page.getResources(), pdfDoc);
        canvas.saveState();
        canvas.setExtGState(new com.itextpdf.kernel.pdf.extgstate.PdfExtGState()
                .setFillOpacity(0.2f));
        canvas.setFillColor(ColorConstants.GRAY);
        Canvas canvasModel = new Canvas(canvas, pageSize);
        Paragraph p = new Paragraph("内部机密");
        p.setFontSize(48);
        p.setTextAlignment(TextAlignment.CENTER);
        canvasModel.showTextAligned(p,
                pageSize.getWidth() / 2, pageSize.getHeight() / 2,
                pdfDoc.getPageNumber(page),
                com.itextpdf.layout.property.TextAlignment.CENTER,
                com.itextpdf.layout.property.VerticalAlignment.MIDDLE);
        canvasModel.close();
        canvas.restoreState();
    }
}

注册监听器只需一行:pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, new WatermarkHandler())。这样不论后面加多少表格,每一页都会自动带上灰色半透明水印。如果希望水印倾斜,可以在Canvas里用setRotationAngle调整,但注意旋转后文本边界可能超出页面,需要适当缩小字号。

水印层必须画在内容之后(newContentStreamAfter),否则会盖住文字。另外透明度通过PdfExtGState设置,直接改canvas的alpha在部分阅读器不生效。生产环境可将水印文字换成公司名或用户ID,便于追踪泄露源头。

三、表格布局与复杂表头

报表核心往往是数据表。iText的Table类支持按列宽比例分配、单元格合并与边框定制。下面示例构建一个三列报表,第一列是序号,后两列是名称和销售额,并演示如何用setBackgroundColor突出表头。

import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.property.UnitValue;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.layout.element.Paragraph;

public class TableDemo {
    public static Table buildTable() {
        // 三列,宽度比例1:3:2
        Table table = new Table(UnitValue.createPercentArray(new float[]{1, 3, 2}));
        table.setWidth(100);
        // 表头
        Cell h1 = new Cell().add(new Paragraph("序号"));
        h1.setBackgroundColor(ColorConstants.LIGHT_GRAY);
        Cell h2 = new Cell().add(new Paragraph("产品"));
        h2.setBackgroundColor(ColorConstants.LIGHT_GRAY);
        Cell h3 = new Cell().add(new Paragraph("销售额"));
        h3.setBackgroundColor(ColorConstants.LIGHT_GRAY);
        table.addHeaderCell(h1);
        table.addHeaderCell(h2);
        table.addHeaderCell(h3);
        // 数据行
        for (int i = 1; i <= 3; i++) {
            table.addCell(new Cell().add(new Paragraph(String.valueOf(i))));
            table.addCell(new Cell().add(new Paragraph("商品" + i)));
            table.addCell(new Cell().add(new Paragraph((i * 100) + "元")));
        }
        return table;
    }
}

addHeaderCell方法会把单元格标记为表头,当表格跨页时自动在每页顶部重复,这对长报表很实用。若需合并单元格,使用new Cell(rowspan, colspan)即可,比如把两行合计行合并成一格。边框粗细通过setBorder方法控制,设为null可去边框做出清爽样式。

中文显示需要在Document初始化时注册字体,否则表格里会出现空白。可以用FontProgramFactory.createFont加载系统宋体或思源字体,再通过PdfFontFactory创建PdfFont传入Paragraph。表格列宽用百分比数组比固定值更能适配不同页面尺寸,避免内容溢出。

四、整合输出完整报表

把前面水印与表格组合,就能得到带背景标识的数据文件。注意Document添加表格后,如果表格很长,iText会自动分页,而我们的END_PAGE事件确保每页都有水印,表头也会重复,整体观感接近Excel打印稿。

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;

import java.io.FileOutputStream;

public class ReportMain {
    public static void main(String[] args) throws Exception {
        PdfWriter writer = new PdfWriter(new FileOutputStream("final_report.pdf"));
        PdfDocument pdfDoc = new PdfDocument(writer);
        // 注册水印事件
        pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, new WatermarkHandler());
        Document doc = new Document(pdfDoc);
        doc.add(new com.itextpdf.layout.element.Paragraph("月度销售报表").setFontSize(20));
        doc.add(TableDemo.buildTable());
        doc.close();
    }
}

运行后打开final_report.pdf,能看到每页居中灰色水印,首页有大标题,随后是带浅灰表头的表格。若部署到Web,把FileOutputStream换成ServletResponse的OutputStream,并设好Content-Type为application/pdf,前端就能直接下载。

整体来看,iText用事件解耦了装饰与内容,用Table抽象了排版细节。掌握这两点,常规报表需求基本可在百行内解决,不必引入重量级报表服务器。

JavaiTextPDF报表修改时间:2026-08-10 06:51:45

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。