1

我们使用 Java 5、Tomcat 5、Xalan 和 JSF 1 构建了一个应用程序,该应用程序使用 XSLT、XML 和 Tomcat 过滤器使用户能够以 Excel 格式导出他们的数据。我们最近升级到 Java 1.7.0_07、Tomcat 7.022 和 JSF 2.1 (jsf-api-2.1.0-b03.jar)。由于所涉及的努力,我们尚未升级到 facelets;我们还是用jsp的。我们使用标签在其自己的弹出窗口中显示 Excel 报告。问题是升级后弹出窗口现在在 IE 中显示原始 xml,而不是直接在 Excel 中打开弹出窗口。原始 xml 可以从浏览器保存到文件中,如果双击保存的文件,它会在 Excel 中正确打开,但最好是用户可以避免这种解决方法。

我认为问题在于 JSF 2 中的响应现在比 JSF 1 更早提交。我们的 web.xml 文件为 Tomcat 定义了以下过滤器:

  <filter>
    <filter-name>XSLT Processor</filter-name>
    <filter-class>com.cs.common.jsf.util.XsltProcessorFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>XSLT Processor</filter-name>
    <url-pattern>*.xml</url-pattern>
  </filter-mapping>

  <filter>
    <filter-name>Hibernate Session Manager</filter-name>
    <filter-class>com.cs.common.hibernate.HibernateSessionServletFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Hibernate Session Manager</filter-name>
    <url-pattern>*.faces</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>Hibernate Session Manager</filter-name>
    <url-pattern>*.xml</url-pattern>
  </filter-mapping>

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xml</url-pattern>
  </servlet-mapping>

我们的 XsltProcesserFilter 类包含以下几行:

fChain.doFilter(request, wrapper);
response.setContentType("application/vnd.ms-excel");

通过使用sysouts,我确定contentType没有在 JSF 2 下设置 ,大概是因为响应已经提交。我已经尝试contentType在输出 xml 的 jsp 中设置 ,但是 JSF 会引发很多错误,所以大概我需要在过程的后面设置它(就像上面的过滤器一样)。response.setBufferSize(6400000)我之前已经尝试过doFilterXsltProcessorFilter因为我已经读过这样做可能会延迟提交,但这也不能解决问题。

如何contentType在面孔完成处理后但在提交之前将其设置为 application/vnd.ms-excel,以便浏览器打开到 Excel 中?

4

1 回答 1

1

上述问题的解决涉及两个问题。第一个问题是 Tomcat 在返回到 XSLTProcessorFilter 之前刷新缓冲区并提交响应。在将控制权交给 Faces 之前,通过在 XSLTProcessorFilter 中将缓冲区大小设置为较大的值来克服这个问题。接下来,Faces 类 JspViewHandlingStrategy 在两点刷新输出。这通过从 XSLTProcessorFilter 添加值为“true”的 isExcelXML 请求属性来克服。然后,在 JspViewHandlingStrategy 中添加编码以检查 isXML 属性,如果其值为 true,则绕过刷新。在这些更改之后,Excel 窗口现在以所需的格式呈现给用户。当然可以简单地注释掉 JspViewHandlingStrategy 中的两个刷新,

我们 XSLTProcessorFilter 的 doFilter 方法现在包含修复:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain fChain) throws IOException, ServletException {
        String xslTemplatePath = request.getParameter(XSLT_REQUEST_PARAM);
        File xslTemplate = (xslTemplatePath == null) ? null : new File(servletPath, xslTemplatePath);

        if ((xslTemplatePath != null) && xslTemplate.exists()) {
            // Run the standard request processing to obtain the XML output
            CharacterResponseWrapper wrapper =  new CharacterResponseWrapper((HttpServletResponse) response);
            response.setBufferSize(6400000); // This line overcomes Tomcat buffer flushing
            request.setAttribute("isExcelXML", "true"); // This line signals to JSF to bypass the flushing
            fChain.doFilter(request, wrapper);
            response.setContentType("application/vnd.ms-excel");

// Transform the XML using the XSL stylesheet specified on the URL
            Source xmlSource = new StreamSource(new StringReader(wrapper.toString()));
            StreamSource xslSource = new StreamSource(xslTemplate);

            try {
                Transformer transformer = tFactory.newTransformer(xslSource);
                StreamResult out = new StreamResult(response.getWriter());              
                transformer.transform(xmlSource, out);

            } catch (Throwable t) {
                t.printStackTrace(response.getWriter());
            }

        } else { // standard processing
            fChain.doFilter(request, response);
        }
    }

JspViewHandlingStrategy 类的更改部分位于其 public void renderView(FacesContext context, UIViewRoot view) 方法的末尾:

//For XML output to Excel, bypass later flushings
boolean bypassFlush = false;
if (((ServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).getAttribute("isExcelXML")!=null
&& ((ServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).getAttribute("isExcelXML").toString().equals("true")) {
    bypassFlush = true;
}

    // write any AFTER_VIEW_CONTENT to the response (This comment in original JSF file)
    // side effect: AFTER_VIEW_CONTENT removed (This comment in original JSF file)
    ViewHandlerResponseWrapper wrapper = (ViewHandlerResponseWrapper)
          RequestStateManager.remove(context, RequestStateManager.AFTER_VIEW_CONTENT);
    if (null != wrapper && !bypassFlush) { //fix to Excel issue involved bypassing flush if isExcelXML set to true
        wrapper.flushToWriter(extContext.getResponseOutputWriter(),
                              extContext.getResponseCharacterEncoding());
    }

    if (!bypassFlush) { //fix to Excel issue involved bypassing flush if isExcelXML set to true
        extContext.responseFlushBuffer();
}

由于这需要数周时间才能解决,因此可能值得包含一些有关如何调试的细节,以防其他人可能使用所使用的技术。

调试从下载 JSF 源代码开始。System.out,println() 语句在其服务方法中以 FacesServlet.java 开头的每个源代码文件中放置。调试的目标是查看 isCommitted 布尔值从“false”切换到“true”的位置,因为这是导致问题的原因。在每个分析的源代码文件中都使用了类似于以下的语句:

System.out.println("someClass someLineId - " + someObjectReference.getClass().getName() +
    ((ServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).isCommitted() +
    ((ServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getContentType());

(请注意,“import javax.servlet.ServletResponse;”通常必须添加到源文件中的 import 语句中。)一旦发现 isCommitted 值在其之前为 false 且在其之后为 true 的行,则切换调试工作到该行调用的实例化类。这个过程一直持续到发现有问题的缓冲区刷新线。

当然,刚才描述的更改必须从项目中运行才能找到问题。必须编译每个源文件(使用包含最终项目的许多 jar 的类路径)。一旦类被编译,原始的 faces jar 被重命名为一个 zip 文件,并且新编译的类通过覆盖已经存在的该文件的版本放入 zip 文件中。然后将 zip 文件重命名回 jar。然后将 jar 放入 Eclipse,重新编译项目,然后运行项目。在 Tomcat 输出窗口中观察到输出。当单个编译产生多个类时(就像内部类可能发生的那样),所有新编译的类都被放置在它们的位置。(尽管这可能不是必需的。)

于 2013-08-29T18:04:27.157 回答