在 Struts 中结合 JFreeChart,iText 生成 PDF 报表
jopen
13年前
<p>本文中向读者朋友提供了一种 PDF 报表系统的解决方案,并将重点放在如何整合开源框架以实现系统要求,以及如何解决实际开发过程中的疑难问题上,对于广大的开源框架爱好者和开发人员具有一定的借鉴意义。该报表解决方案主要提供以下功能:</p> <ol type="1"> <li>用户图片上传</li> <li>根据数据生成柱状图,折线图</li> <li>汇总数据及图片,最终以 PDF 进行呈现</li> </ol> <p><a><span class="atitle">开源框架整合</span></a></p> <p>基于对开源领域中比较成熟的框架的比较,我们最终选择以 Struts 为基础,整合 common-fileupload,JFreechart 和 iText,以实现上述的系统功能。下面我们来讲述如何整合这些开源框架,建立开发环境。</p> <p><a><span class="smalltitle">Struts</span></a></p> <p>Struts(http://struts.apache.org/) 是广泛使用的 MVC 框架,相信很多开发人员都非常熟悉,这不是本文的重点,仅会提及一个技术点,即如何在 Struts 框架中实现文件上传,将在下文中详细描述。</p> <p><a><span class="smalltitle">Commons-fileupload</span></a></p> <p>Commons-fileupload (http://commons.apache.org/fileupload/) 能与 Servlet 及 Web application 很好地结合,基于对 Http request 的解析,可以被方便灵活地调用,从而提供高性能的文件上传功能。</p> <p><a><span class="smalltitle">JFreeChart</span></a></p> <p>JFreeChart(http://www.jfree.org/) 主要是用来制作各种各样的图表,包括:饼图、柱状图 ( 普通柱状图以及堆栈柱状图 )、线图、区域图、分布图、混合图、甘特图以及一些仪表盘等等。本文中使用的是 jfreechart-1.0.13.jar。</p> <p><a><span class="smalltitle">iText</span></a></p> <p>iText(http://itextpdf.com/) 是一个能够快速产生 PDF 文件的 Java 类库,与 Servlet 有很好的给合,可以非常方便完成 PDF 输出,最新的版本是 iText-5.0.4.jar。如果需要在报表中支持中文显示,还需要下载 iTextAsian.jar。</p> <p><a><span class="smalltitle">Eclipse 中整合开源框架</span></a></p> <p>为完成开发框架的搭建工作,我们需要将上述的几个开源框架整合到 Eclipse 中。读者需要通过以上的链接下载相应的各个 Jar 包,然后导入到工程的类路径下。可以参考 Eclipse 的相关资料来完成这些配置操作。</p> <p><a><span class="atitle">Struts 中的文件上传</span></a></p> <p>基于 Struts 框架实现文件上传需要注意以下两点:</p> <ol type="1"> <li>Form 需要增加属性: enctype="multipart/form-data"。<br /> 如:<form method="post" id="reportForm" enctype="multipart/form-data"></li> <li>需要直接继承自 Action,而不能是 DispatchAction。</li> </ol> <p>以上两点需要在开发过程中加以注意,否则使用 ServletFileUpload 的 parseRequest() 方法解析 request 的时候,得不到 Form 中 file 域传递的值。JSP 页面的代码在这里不再赘述,下面通过代码及注释来说明 Commons-fileupload 的使用,读者可以根据实际需要设定上传图片的保存路径和文件的名称。</p> <p><br /> <a><strong>清单 1. 使用 Commons-fileupload 上传文件</strong></a></p> <pre class="displaycode"> DiskFileItemFactory factory = new DiskFileItemFactory(); // 实例化硬盘文件工厂 factory.setSizeThreshold(8192);// 存放临时文件的内存大小 String tempPath = request.getRealPath("/") + "/images/temp"; if(!new File(tempPath).isDirectory()) new File(tempPath).mkdirs(); factory.setRepository(new File(tempPath)); // 设置上传路径 uploadPath = request.getRealPath("/") + "/web/report/images/"; if(!new File(uploadPath).isDirectory()) new File(uploadPath).mkdirs(); // 初始化上传组件,循环 form 中的所有 input 类型为 file 的 field,上传文件 ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items = upload.parseRequest(request); Iterator<FileItem> itr = items.iterator(); while (itr.hasNext()) {// 依次处理每个 form field FileItem item = (FileItem) itr.next(); File savedFile = new File(uploadPath, "imageFileName.jpg"); item.write(savedFile); } </pre> <p><a><span class="atitle">JFreechart 生成报表</span></a></p> <p>最新的 struts 2.0 已经集成了 JFreechart,作为一种 ResultType 在 Action 中可以直接返回,读者如果感兴趣可以参考其他相关的资源。本文中我们介绍如何基于 Struts 1.x 版本来集成使用 JFreechart。</p> <p><a><span class="smalltitle">生成图片报表</span></a></p> <p>这里我们封装了一个实用类 ChartUtil,它继承自 JFreeChart 的 ServletUtilities 类,来提供本文所述报表方案的所有生成 JFreechart 报表的功能。清单 2 中给出了如何生成一个柱状图的方法,该方法的调用将会在清单 6 中看到。</p> <p><br /> <a><strong>清单 2. 封装 JFreechart 的 util 类,生成柱状图</strong></a></p> <table class="ke-zeroborder" border="0" cellspacing="0" cellpadding="0" width="100%"> <tbody> <tr> <td><pre class="displaycode"> public static String generateBarChart(HttpServletRequest request, CategoryDataset dataset, int w, int h,double rangeMarker) throws IOException { JFreeChart chart = ChartFactory.createBarChart3D("Latency in ms", // 图表标题 "Time", // 目录轴的显示标签 "Milliseconds", // 数值轴的显示标签 dataset, // 数据集 PlotOrientation.VERTICAL, // 图表方向:水平、垂直 true, // 是否显示图例 ( 对于简单的柱状图必须是 false) false, // 是否生成工具 false // 是否生成 URL 链接 ); setAttribute(chart); createTempDir(request); chart.getCategoryPlot().addRangeMarker(new ValueMarker(rangeMarker)); String tempDirName = request.getRealPath("/") + "/web/report/images/temp/"; String prefix = ServletUtilities.getTempFilePrefix(); if (request.getSession() == null) { prefix = ServletUtilities.getTempOneTimeFilePrefix(); } File tempFile = File.createTempFile(prefix, ".png", new File(tempDirName)); try { ChartRenderingInfo info = new ChartRenderingInfo( new StandardEntityCollection()); ChartUtilities.saveChartAsPNG(tempFile, chart, w, h, info); } catch (IOException e) { e.printStackTrace(); } return tempDirName + tempFile.getName(); } </pre></td> </tr> </tbody> </table> <p> </p> <p><a><span class="smalltitle">修改默认图片保存路径</span></a></p> <p>JFreeChart 默认将生成的图片保存在应用服务器的 temp 目录下,有些时候,我们不能使用默认的保存路径,而是需要能够再次通过应用程序读取这些图片,这时就需要将图片保存在应用程序的目录下。下面,我们将介绍 如何实现对默认图片保存路径的修改,即在 ChartUtil 类中重写 createTempDir 方法,将图片保存在应用程序的 /web/report/images/temp 目录下。</p> <p><br /> <a><strong>清单 3. 重写 createTempDir 方法</strong></a></p> <table class="ke-zeroborder" border="0" cellspacing="0" cellpadding="0" width="100%"> <tbody> <tr> <td><pre class="displaycode"> protected static void createTempDir(HttpServletRequest request) { String tempDirName = request.getRealPath("/") + "/web/report/images/temp"; if (tempDirName == null) { throw new RuntimeException("Temp directory is null."); } File tempDir = new File(tempDirName); if (!tempDir.exists()) { tempDir.mkdirs(); } } </pre></td> </tr> </tbody> </table> <p> </p> <p><a><span class="smalltitle">如何定制图片样式</span></a></p> <p>JFreeChart 提供了对图片格式修改的各种 API,包括对图片背景,文字显示,曲线,坐标等等一系列的格式设置,读者可以根据实际需要查询相应的 API 来实现。比如,上面的清单 2 中,我们有如下一行代码,实现了在柱状图中增加一条阈值线。</p> <p><a><strong>清单 4. 柱状图中增加阈值线</strong></a></p> <pre class="displaycode"> chart.getCategoryPlot().addRangeMarker(new ValueMarker(rangeMarker)); </pre> <p><a><span class="atitle">iText 生成 PDF 报表</span></a></p> <p>iText 是一个 Java 类库,可以方便地创建,读取和操作 PDF 文件。下面我们通过一个最简单的例子来说明如何使用 iText。然后重点介绍一下几个比较重要的 object,并针对最常见的使用过程中遇到的问题给出一些建议。</p> <p><br /> <a><strong>清单 5. iText 的简单使用</strong></a></p> <table class="ke-zeroborder" border="0" cellspacing="0" cellpadding="0" width="100%"> <tbody> <tr> <td><pre class="displaycode"> //set pdf location and the title String pdfLocation = request.getRealPath("/") + "/web/report/pdf/"; String pdfName= "test.pdf"; String pdfFile = pdfLocation + pdfName; Document document = new Document(); try { PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFile)); document.open(); document.add(new Paragraph("Hello world")); } catch (DocumentException de) { System.err.println(de.getMessage()); } catch (IOException ioe) { System.err.println(ioe.getMessage()); } finally { document.close(); } </pre></td> </tr> </tbody> </table> <p> </p> <p><a><span class="smalltitle">iText 结构概览</span></a></p> <p>从上面的代码片段中,我们可以看到 Document 对象是 iText 的基础,从 open,add,close 的调用过程,我们也可以很容易理解 iText 的使用。</p> <p>iText 对于对象的组织,也是像实际的 PDF 文件中一样,是一种分级结构。Chunk(块)是 iText 中最小也是最重要的可以被加入到 Document 中的文本块,绝大部分的元素都可以被分割成 Chunk。Phrase 由一个或多个 Chunk 组成,它有一个主字体样式,同时包含在其中的 Chunk 可以通过设置使用不同于 Phrase 的其他字体样式。</p> <p>下面我们通过代码片段来看看如何将图片写入到 PDF 文件中,其实,这与写入普通的文本并没有实质区别。我们通过 dataService 获取生成 JFreeChart 的数据集,然后调用 ChartUtil 生成图片并返回图片文件的路径及名称,进而得到一个 com.itextpdf.text.Image 对象,将它增加到一个 PdfPTable 中写入 PDF。</p> <p><br /> <a><strong>清单 6. 用 iText 将 JFreechart 图表写入 PDF</strong></a></p> <table class="ke-zeroborder" border="0" cellspacing="0" cellpadding="0" width="100%"> <tbody> <tr> <td><pre class="displaycode"> // 初始化一个 PdfPTable PdfPTable BarTable = new PdfPTable(1); PdfPCell cellDescription = new PdfPCell(new Paragraph("This is a image from JFreechart")); cellDescription .setBorder(PdfPCell.NO_BORDER); BarTable.addCell(cellDescription ); // 生成柱状图 CategoryDataset latencyDataset = dataService.getLatencyDataset(nodeA, nodeB, month); String fileName = ChartUtil.generateBarChart(request, latencyDataset, 500, 200,link.getLatency()); Image imageLatency = Image.getInstance(fileName); // 加到 table 中 PdfPCell cellImageLatency = new PdfPCell(imageLatency); cellImageLatency.setBorder(PdfPCell.NO_BORDER); cellImageLatency.setHorizontalAlignment(Element.ALIGN_CENTER); BarTable.addCell(cellImageLatency); // 生成曲线图 CategoryDataset lossDataset = dataService.getLossDataset(nodeA, nodeB, month); fileName = ChartUtil.generateLineChart(request, lossDataset, 500, 200); Image imageLoss = Image.getInstance(fileName); // 加到 table 中 PdfPCell cellImageLoss = new PdfPCell(imageLoss); cellImageLoss.setBorder(PdfPCell.NO_BORDER); BarTable.addCell(cellImageLoss); // 写入 PDF document.add(BarTable); </pre></td> </tr> </tbody> </table> <p> </p> <p><a><span class="smalltitle">iText 支持中文</span></a></p> <p>为了让 iText 支持中文,我们需要下载语言包并加入到类路径中。最新的 iText-5.0.4.jar 对包结构进行了更改,由 com.lowagie.text.pdf.fonts 更新为 com.itextpdf.text.pdf.fonts,但是 iTextAsian.jar 依旧是沿用旧的包结构,因此我们需要修改 iTextAsian.jar 的包的结构,才能正确支持中文。</p> <p><br /> <a><strong>清单 7. iText 中如何支持中文字体</strong></a></p> <pre class="displaycode"> BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); Font chineseFont= new Font(bfChinese, 12, Font.NORMAL); PdfPCell cellReportSummary = <strong>new</strong>PdfPCell(<strong>new</strong>Phrase("支持中文",chineseFont)); </pre>