带有 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 问题阻止输出流将报告导出到浏览器。
我正在使用 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 问题阻止输出流将报告导出到浏览器。