老生常谈:struct2文件下载(带点击取消出现的异常解决办法)

       网上关于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.youkuaiyun.com/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






      






   







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值