網上關於Struts2文件下載的文章太多了,但對於新手來說,要查閱到使用完整的Demo卻是非常困難,或者說實現“成功”下載後便置之不理了,但當項目投入生產環境後,出了問題卻無法解決,筆者根據自己的經驗,下面介紹較爲完整的可用於實際項目的Struts2文件下載Demo。
1.Struts文件下載常見配置(基礎配置)
<!-- 處理文件下載請求的action -->
<action name="fileDownload" class="my.pro.FileDownloadAction">
<!-- 對於文件下載,這裏的type必須是stream -->
<result type="stream" name="downLoad">
<!-- 文檔類型,下面這種是通用類型,適用於任何文檔類型 -->
<param name="contentType">application/octet-stream</param>
<!-- 輸入流名稱 類my.pro.DownLoadFile中與之對應的方法爲getTarget(),且返回值爲InputStream -->
<param name="inputName">targetFile</param>
<!-- 下載時的文件名 -->
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<!-- 緩衝區大小 -->
<param name="bufferSize">4096</param>
</result>
</action>
2.下載類FileDownloadAction的寫法
public class FileDownloadAction extends ActionSupport{
private static final long serialVersionUID = 1L;
//該屬性是依賴注入的屬性,該屬性可以在配置文件中動態指定該屬性值
private String fileName;
//依賴注入該屬性值的setter方法
public void setFileName(String fileName) {
this.fileName = fileName;
}
/*
下載用的Action應該返回一個InputStream實例,
該方法對應在result裏的inputName屬性值爲targetFile
*/
public InputStream getTargetFile() throws Exception
{
/*可以放入業務邏輯處理*/
return ServletActionContext.getServletContext().getResourceAsStream(fileName);
}
//處理用戶請求的execute方法,該方法返回success字符串
@Override
public String execute() throws Exception
{
return SUCCESS;
}
public String getFileName() {
return fileName;
}
}
這裏得介紹下ServletActionContext.getServletContext().getResourceAsStream( )了,這裏用不用此方法得取決了你的文件存放位置,這裏不是指絕對路徑,而是相對於classpath環境變量的相對路徑。在開發環境下,一般src目錄包含在classpath下的,在tomcat下,一般指向WEB-INF/classes目錄,不過通常情況下,開發者們都不想將待下載的文檔置於網站目錄下,而是放在服務器的某個路徑下便於管理,這樣就必須在tomcat中設置context參數指向這個路徑。如果讀者不想這樣做,而是想在下載時直接打開服務器上的文件絕對路徑,就必須換個方法了,如下:
public InputStream getTargetFile() throws Exception
{
/*可以放入業務邏輯處理*/
File file = new File("文件絕對路徑");
/*這裏可考慮算法的健壯性,如文件不存在時的處理,當然用try catch了*/
return new FileInputStream(file);
}
通過上面的介紹,讀者可以實現的下載了,當然很多初學者弄到這步也就沾沾自喜了。不過等待着的將是一個讓人頭疼的異常,有人爲此奮戰許久。那是什麼異常呢,先賣個關子。我們先說一個用戶行爲,當我們在下載文件時,如果發現網速太慢或是其他情況,通常會取消下載。下面要說的就是這個問題,用Struts2實現文件下載時點擊取消帶來的異常(保存或打開不會出現),如下:
嚴重: Servlet.service() for servlet default threw exception
java.lang.IllegalStateException
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:407)
at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:108)
at com.opensymphony.module.sitemesh.filter.PageResponseWrapper.sendError(PageResponseWrapper.java:176)
或者:Broken pipe
爲什麼會出現此異常呢?下面講講原理(理解原理,對於解決問題是必要的),在Struts2的配置文件中,對於下載的配置參數用的是stream,stream對應的類是org.apache.struts2.dispatcher.StreamResult,其處理過程是:
(1)配置其中result標籤下的各個參,如:
<param name="contentType">application/octet-stream</param>
<!-- 輸入流名稱 類my.pro.DownLoadFile中與之對應的方法爲getTarget(),且返回值爲InputStream -->
<param name="inputName">targetFile</param>
<!-- 下載時的文件名 -->
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<!-- 緩衝區大小 -->
<param name="bufferSize">4096</param>
(2)當請求文件下載後,服務器響應後開始獲取輸入流,並同時與客戶端建立輸出流,服務器與客戶端鏈接通過Socket進行連接。(3)數據開始傳輸。在傳輸的過程中,如果點擊了“取消”,代表關閉了所有的流,但實際上Socket沒有斷開,就是流還沒有關閉,所以在JSP容器通過Response獲取輸出流之前,前面的流並沒有關閉,故而出現異常。
下面給出解決辦法。
3. Struts2下載文件點擊取消出現的異常解決辦法
(1)逃避辦法
這是一種異常攔截方法,當發生ClientAbortException異常時,跳轉到指定的頁面,而這個頁面什麼都不用輸出,實際上異常依然發生,只不過不顯示,當看不見,有點“掩耳盜鈴”的意思,所以叫“逃避辦法”,筆者不推薦這個方,畢竟治標不治本。示例如下:
<package name="default" extends="struts-default">
<global-results>
<result name="client-abort-exception">err.jsp</result>
</global-results>
</package>
<package name="mypck" extends="struts-default">
<exception-mapping result="client-abort-exception" exception="org.apache.catalina.connector.ClientAbortException"/>
<action name="download" class="my.pro.FileDownloadAction">
<result name="success" type="stream">
<para mname="inputName">targetFile</param>
<param name="contentDisposition">filename=""</param>
<param name="buffersize">4096</param>
</result>
</action>
</package>
(2)根治方法前面分析了tream對應的類是org.apache.struts2.dispatcher.StreamResult的執行過程,要徹底解決這個問題,還需從它入手。如果將其換成能代替的類,並且這個類不會出現這個異,原因是點擊“取消”的同時關閉流,不會再報出該異常,就可以根治了。這裏用struts2-sunspoter-stream-1.0.jar代替之,注意是1.0。方法如下:
第一步:先下載struts2-sunspoter-stream-1.0.jar,並複製到項目的/WEB-INF/lib下。下載地址爲:http://download.csdn.net/detail/xiangchengguan/8010633
第二步:在原有的struts.xml的基礎上進行相應的配置,先在〈package〉包的第一個節點前(否則會出錯)插入<result-types>,如下:
<package name="default" namespace="/"extends="struts-default">
<!-- 添加如下內容 -->
<result-types>
<result-type name="streamx"class="com.sunspoter.lib.web.struts2.dispatcher.StreamResultX"/>
</result-types>
……
</package>
接下來就是將下載類配置中的stream換成streamx啦,共他不變,如下:
<!-- 處理文件下載請求的action -->
<action name="fileDownload" class="my.pro.FileDownloadAction">
<!-- 對於文件下載,這裏的type必須是stream -->
<result type="streamx" name="downLoad">
<!-- 文檔類型,下面這種是通用類型,適用於任何文檔類型 -->
<param name="contentType">application/octet-stream</param>
<!-- 輸入流名稱 類my.pro.DownLoadFile中與之對應的方法爲getTarget(),且返回值爲InputStream -->
<param name="inputName">targetFile</param>
<!-- 下載時的文件名 -->
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<!-- 緩衝區大小 -->
<param name="bufferSize">4096</param>
</result>
</action>
這樣就算大功告成了,點擊“取消”的同時也關閉了流,不會再報出該異常。如果配置了log4j.properties,當用戶點擊“取消”時,將出現如下警告,Socket非正常中斷,但流已經關閉。如下:
WARN StreamResult:45 - StreamResultX Warn : socket write error