在我們做struts2文件下載的時候,經常會遇到這種問題:點“打開/保存”一切正常,但當我們點擊“取消”時,卻報一堆的異常,非常讓人頭疼,錯誤如下(每個人的錯誤估計不太一樣,以我的為例):
2011-5-19 10:30:23 org.apache.catalina.core.ApplicationDispatcher invoke
嚴重: Servlet.service() for servlet jsp threw exception
java.lang.IllegalStateException: getOutputStream() has already been called for this response
at org.apache.catalina.connector.Response.getWriter(Response.java:611)
at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:198)
at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:112)
at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:112)
at org.apache.jasper.runtime.JspWriterImpl.initOut(JspWriterImpl.java:125)
at org.apache.jasper.runtime.JspWriterImpl.flushBuffer(JspWriterImpl.java:118)
at org.apache.jasper.runtime.PageContextImpl.release(PageContextImpl.java:180)
at org.apache.jasper.runtime.JspFactoryImpl.internalReleasePageContext(JspFactoryImpl.java:118)
at org.apache.jasper.runtime.JspFactoryImpl.releasePageContext(JspFactoryImpl.java:77)
at org.apache.jsp.layout.adminlayout_jsp._jspService(adminlayout_jsp.java:204)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:377)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.struts2.dispatcher.FilterDispatcher.doFilter(FilterDispatcher.java:389)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:39)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:646)
at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:551)
at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:488)
at com.opensymphony.module.sitemesh.filter.PageFilter.writeDecorator(PageFilter.java:173)
at com.opensymphony.module.sitemesh.filter.PageFilter.applyDecorator(PageFilter.java:158)
at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:62)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.struts2.dispatcher.ActionContextCleanUp.doFilter(ActionContextCleanUp.java:102)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:164)
at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:417)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:619)
異常原因分析:
[stream對應的類是org.apache.struts2.dispatcher.StreamResult,該類的處理過程如下:
- 配置其中result標簽下的各個參數
- 從服務器中獲取輸入流,并同時與客戶端建立輸出流(服務器與客戶端鏈接通過Socket進行連接)
- 當點擊“保存”或“打開”時,開始傳輸數據。如果點擊“取消”,關閉所有的流
這里要注意的是,但是實際發現Socket并沒有斷開!并且流也沒有關閉!這一點非常重要!
所以在JSP容器通過Response獲取輸出流之前,前面的流并沒有關閉,所以會造成該異常的報出。
我在CSDN上搜了一個人的解決方法,他是這么解決的:
<package name="default" extends="json-default" namespace="/">
<!-- 定義全局Result -->
<global-results>
<result name="client-abort-exception">/null.jsp</result>
</global-results>
</package>
<package name="main" extends="default" namespace="/">
<action name="download" class="fileAction" method="download">
<exception-mapping result="client-abort-exception" exception="org.apache.catalina.connector.ClientAbortException"></exception-mapping>
<param name="savePath">/upload/download</param>
<!-- 文件下載配置結果類型為stream的結果 -->
<result name="download" type="stream">
<param name="inputName">targetFile</param>
<!-- 指定保存還是直接打開要下載的文件默認為:直接打開,這里配置保存 -->
<param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
<!-- 指定下載文件的緩存大小 -->
<param name="bufferSize">4096</param>
</result>
</action>
</package>
也就是說,如果拋出了ClientAbortException異常,那就跳轉到“null.jsp”這個頁面,這個頁面中什么內容都沒有。雖然這種方法暫時可行,但是當遇到其他異常的時候,也就是非ClientAbortException之后,這個方法就不可行了。我之前也是參照這種方法配置,但昨天我重新啟動應用的時候,點擊“取消”居然還報錯,錯誤就是上面的一堆“java.lang.IllegalStateException”,而且這種方法也是一種逃避的方法,也就是置之不理。這并不可取,解決問題就要解決徹底明了,逃避是沒用的,經過網上大師們的分析測試,最終解決辦法如下:
- 下載一個插件struts2-sunspoter-stream-1.0.jar(附件中有下載)
- 將附件解壓獲取struts2-sunspoter-stream-1.0.jar,并復制在/WEB-INF/lib下
- 在原有的struts.xml的基礎上進行相應的配置,配置如下:
<package name="main" extends="default" namespace="/">
<!-- 添加如下內容 -->
<result-types>
<result-type name="streamx" class="com.sunspoter.lib.web.struts2.dispatcher.StreamResultX"/>
</result-types>
<action name="download" class="fileAction" method="download">
<param name="savePath">/upload/download</param>
<!-- 文件下載配置結果類型為streamx -->
<result name="download" type="streamx">
<param name="inputName">targetFile</param>
<!-- 指定保存還是直接打開要下載的文件默認為:直接打開,這里配置保存 -->
<param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
<!-- 指定下載文件的緩存大小 -->
<param name="bufferSize">4096</param>
</result>
</action>
</package>
在這種方式下,只需添加一個result-type,將原有的result中type改為“streamx”,其他一律不變,在這種情況下,點擊“取消”的同時也關閉了流,不會再報出該異常。
如果出現log4j的警告,比如:
21:23:44,676 WARN StreamResult:45 - StreamResultX Warn : socket write error
出現該警告說明正確執行,該警告說明,Socket非正常中斷,但是流確實已經關閉,自此再也不用看到上面出現的討厭異常結果。