文件下载,可以通过Servlet中的写法,也可以通过Struts框架做。已经做过这个功能很多次了。用的多了,这次突然发现了一个问题。
在项目中,做了一个下载功能,在好几个页面都用了这个功能,相同的方法,在不同页面引用。
调用这个功能,用URL重写的方式调用action中的方法。
这里的下载功能没有使用struts框架,用的servlet中的
一、探索过程
(1)刚开始我以为,在不同页面上调用这个方法,方法虽然相同,但调用方法后回到的页面不同。所以我把这个公用的方法提出来,作为action中private方法,不同页面请求Action中的不同方法,这些方法都调用这个private方法,这些action返回值回到各自的页面。但后来证明不需要这么麻烦。就把这个称为方法一。
(2)然后我觉得方法一太麻烦了。想把下载方法放到action中,然后在页面上用ajax访问这个action中的方法。不同的页面都走同样的ajax请求,这样,这个方法就复用了。因为项目框架中用的是dwr。所以我也直接用dwr发送这个请求了。写完之后,下载功能报错。调试后发现,是在HttpServletResponse response=ActionContext.getHttpServletResponse();时报的错response获取不到,后来我换成ResponseAware注入到Action中的方式获取response,还是获取不到,response为null。上网查原因,网上是这么说的:
Action中获取response有两种方式:ActionContext.getHttpServletResponse()和ResponseAware注入。只有当请求通过struts.xml文件时,才能获取到response。
因为dwr并没有走struts.xml,而是通过自己的配置文件dwr.xml,所以通过dwr向action发送请求,是获取不到response的。没有response也就无法使用FileOutputStream out=response.getOutputStream();没有输出流,文件下载就失败。就把这个失败的方法称为方法二吧。
(3)既然dwr不行,然后我就想换成jQuery来做这个ajax效果。jQuery中ajax请求是走struts.xml的,获取response肯定没问题。把这个还未用实践尝试的方法称为方法三。
(4)中间和同事说了,他也是突然一愣。我俩讨论了下,发现:
action中下载的方法,代码大致如下:
1 public String downloadFile(){ 2 //要下载的文件 3 File file = new File("F:/upload/课程.png"); 4 //说明下载文件的类型 5 resp.setContentType("image/png");//应该写一个工具类或方法,根据文件名后缀,获得文件MIME类型 6 //说明下载文件的长度,进度条,以便用户看下载进度 7 resp.setContentLength(Long.valueOf(file.length()).intValue()); 8 //文件名有中文时,为了在弹出的保存框中显示中文,转码。浏览器会相反方向转码,即ISO8859-1→UTF-8 9 String fileName=new String("1.png".getBytes("UTF-8"),"ISO8859-1"); 10 //出现弹出框,并显示下载的文件名 11 resp.setHeader("Content-Disposition", "attachment;filename=""+fileName+""");//响应头 12 13 InputStream inputStream = new FileInputStream(file); 14 //获得响应输出流,输出到网页上 15 OutputStream outputStream =resp.getOutputStream();//返回ServletOutputStream 16 17 BufferedOutputStream bos=new BufferedOutputStream(outputStream); 18 BufferedInputStream bis=new BufferedInputStream(inputStream); 19 20 byte[] buffer=new byte[100];//缓冲区大小影响下载速度 21 int len=-1; 22 23 while((len=bis.read(buffer))!=-1){ 24 bos.write(buffer, 0, len); 25 } 26 bos.flush(); 27 bos.close(); 28 bis.close(); 29 30 System.out.println("下载完成!"); 31 32 return null; 33 }
执行下载程序的时候,方法可以返回null或者没有返回值。下载程序返回null或者没有返回值的时候,不用给struts.xml中的action配置<result>,下载之后,页面也不会跳转,仍然保持在原页面。所以,action中的这个方法是可以在系统中的不同页面上直接调用的,这个方法也不用再像上面那样各种处理,直接调用就行了。如:
1 <input type="button" onclick="download('${ctx}',${id});" value="下载" />
1 function download(ctx,id){ 2 location.href=ctx+"/doActivity/downloadFile.do?id="+id; 3 }
1 <action name="downloadFile" class="activityOrganAction" method="downloadFile"> 2 </action>
除了Ajax的请求,这个方法和方法三几乎没有什么不同。
二、总结
当:多个页面共用一个下载方法,方法后台没有返回值或者返回null,那么在struts.xml中不用配置<result>,下载之后页面会停留在当前页面,不会跳转。在这些页面可以直接调用这同一个下载方法。
代码写久了,下载的方法用多了,慢慢也就在这些细节上模糊了,这次算是弄清楚这个问题了。
2018.4.2
action属于MVC的C层,公用的部分不适合在这里做公共的。
下载的方法属于共用方法,这里用的是servlet,可以抽出来放到工具类中,如PubMethod,或PubUtil,由各个action调用,把file传参。