带有 OutputStream 的 JasperReport 不导出为 PDF

JasperReport with OutputStream not exporting to PDF

我正在使用 JasperReport 将报告导出为 PDF。代码 运行 没有异常消息出现在 console/log 中。但是,报告不会导出到浏览器。换句话说,正在创建报告,我只是无法下载或访问它。

导出代码如下:

public void generatePDFReport(Map<String, Object> parameters, JRDataSource jrDataSource, String resource, String filename)
{
    OutputStream os = null;
    try{
        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
        os = response.getOutputStream();

        InputStream reportTemplate = this.getClass().getClassLoader().getResourceAsStream(resource);
        byte[] pdf = null;

        try {
            JasperDesign masterDesign = JRXmlLoader.load(reportTemplate);
            masterReport = JasperCompileManager.compileReport(masterDesign);
            masterReport.setWhenNoDataType(WhenNoDataTypeEnum.ALL_SECTIONS_NO_DETAIL);
            JasperPrint masterPrint = JasperFillManager.fillReport(masterReport, parameters, jrDataSource);
            pdf = JasperExportManager.exportReportToPdf(masterPrint);
        } catch (JRException e) {
            log.error(e);
        }
        response.setContentType("application/pdf");
        response.setContentLength(pdf.length);
        response.setHeader("Content-disposition", "attachment; filename=\""+filename+"\"");

        context.responseComplete();

        os.write(pdf);

        pdf = null;
    }catch(Exception e){
        log.error(e);
    }finally{
        try{
            os.flush();
            os.close();
        }catch(IOException e){
            log.error(e);
        }
    }
}

我几乎 100% 确定该代码没有任何问题,因为它适用于不同的报告(我 运行 对其他几个报告使用相同的导出代码,并且它对所有报告都按预期工作除了这个)。

这么一想,我想这肯定和报告本身有关。该报告是一个 jrxml JasperReport 文件。该报告是使用 iReport 创建的。但是,我修改了上面的代码,将其简单地保存到下载文件夹中,并且可以很好地创建报告。

所以,问题是报告在后端成功创建,但没有按预期发送到前端(浏览器)。

我乐于接受任何有关此报告为何不起作用的建议。

运行 bean 中的代码有问题,因为:

  • 每个 HTTP 请求只允许调用一次 getOutputStream
  • Web 框架 (J2EE/JSF) 可能已经编写了 HTTP headers
  • JSF 页面可能已被写为 HTML 在临时缓冲区中(调用 responseComplete() 时刷新)。
  • 可以重置 headers,但这对 getOutputStream 问题没有帮助
  • 调用 responseComplete() 会将任何 HTML 连同 PDF 内容一起刷新到浏览器

使用 servlet。 servlet 的发送方法不必比以下内容更复杂:

protected void send(final byte[] content) throws IOException {
    setContentLength(content.length);

    try (final OutputStream out = getOutputStream()) {
        out.write(content);
    }
}

同时考虑设置缓存,这样就不可能出现陈旧的报告:

protected void disableCache() {
    // https://tools.ietf.org/html/rfc7234#section-7.1.3
    setHeader(CACHE_CONTROL, "private, no-store, no-cache, must-revalidate");

    // https://tools.ietf.org/html/rfc7234#section-5.3
    setHeader(EXPIRES, "Thu, 01 Dec 1994 16:00:00 GMT");

    // https://tools.ietf.org/html/rfc7234#section-5.4
    setHeader(PRAGMA, "no-cache");

    // https://tools.ietf.org/html/rfc7232#section-2.2
    setHeader(LAST_MODIFIED, getServerTimestamp());
}

private String getServerTimestamp() {
    final SimpleDateFormat rfc1123 = new SimpleDateFormat(DATE_RFC_1123, getDefault());

    rfc1123.setTimeZone(getTimeZone("GMT"));

    final Calendar calendar = Calendar.getInstance();
    return rfc1123.format(calendar.getTime());
}

这意味着,例如:

@WebServlet(
        name = "ReportServlet",
        urlPatterns = {PATH_SERVLET + "ReportServlet"}
)
public class ReportServlet extends AbstractServlet {
}

然后使用常规锚点link:

<h:outputLink value="/app/path/servlet/Reportservlet">Run Report</h:outputLink>

总而言之,不要通过拦截对 JSF 页面的请求来发送二进制报告数据;请改用 servlet。

servlet 和 JSF 页面之间的通信可以通过以下方式进行:

  • Session 变量(在 servlet 端的 HTTPSession)
  • URL 参数

Servlet 的优点是完全避免了 JSF 开销,这将使报告 运行 从用户的角度来看更快。另外,不要编译报告——直接使用 .jasper 文件,这也会有性能改进。 (我并不是说使用 .jrxml 文件是问题所在,只是这不是必要的步骤。)

我找到了解决问题的方法。最终,我发现报告生成代码或报告没有任何问题,但有一个 ajax 问题阻止输出流将报告导出到浏览器。