在 Struts 中结合 JFreeChart,iText 生成 PDF 报表

jopen 12年前
     <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>