http://pengranxiang.iteye.com/blog/259401

一、通过struts2提供的下载机制下载文件:

  项目名为 struts2hello ,所使用的开发环境是MyEclipse 6,当然其实用哪个IDE都是一样的,只要把类库放进去就行了,文件下载不需要再加入任何额外的包。读者可以参考文档:http://beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf ,来了解怎么下载和配置基本的Struts 2开发环境。

为了便于大家对比,我把完整的struts.xml的配置信息列出来:

  第一步:编写一个普通的Action就可以了,只需要提供一个返回InputStream流的方法,该输入流代表了被下载文件的入口,这个方法用来给被下载的数据提供输入流,意思是从这个流读出来,再写到浏览器那边供下载。这个方法需要由开发人员自己来编写,只需要返回值为InputStream即可。在我们的例子中方法的签名是:public InputStream getInputStream() throws Exception ,当然它也可以是别的名字,例如getDownloadFile() 。好了,现在我们所写的这个进行文件下载的Action类example.FileDownloadAction 的源代码清单如下: 

  1. package example;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.InputStream;
  4. import com.opensymphony.xwork2.Action;
  5. public class FileDownloadAction implements Action {
  6.  
  7. public InputStream getInputStream() throws Exception {
  8. return new ByteArrayInputStream("Struts 2 下载示例".getBytes());
  9. }
  10.  
  11. public String execute() throws Exception {
  12. return SUCCESS;
  13. }
  14.  
  15. }

注意这里唯一特殊的方法就是getInputStream() ,在这个方法里面我们使用了一个数组输入流来从字符串转换成的数组作为数据的来源进行读取。也许方法体中使用这样的实现代码:

  第二步:乃是在struts.xml 中对action进行配置,其代码清单如下所示:

  1. <!-- 简单文件下载 -->
  2. <action name="download" class="example.FileDownloadAction">
  3. <result name="success" type="stream">
  4. <param name="contentType">text/plain</param>
  5. <param name="inputName">inputStream</param>
  6. <param name="contentDisposition">attachment;filename="struts2.txt"</param>
  7. <param name="bufferSize">4096</param>
  8. </result>
  9. </action>

  这个action特殊的地方在于result的类型是一个流(stream ),配置stream类型的结果时,因为无需指定实际的显示的物理资源,所以无需指定location 属性,只需要指定 inputName 属性,该属性指向被下载文件的来源,对应着Action类中的某个属性,类型为InputStream。下面则列出了和下载有关的一些参数列表:

  参数说明

  1. contentType 内容类型,和互联网MIME标准中的规定类型一致,例如text/plain代表纯文本,text/xml表示XMLimage/gif代表GIF图片,image/jpeg代表JPG图片
  2.  
  3. inputName 下载文件的来源流,对应着action类中某个类型为Inputstream的属性名,例如取值为inputStream 的属性需要编写getInputStream()方法
  4.  
  5. contentDisposition 文件下载的处理方式,包括内联(inline)和附件(attachment)两种方式,而附件方式会弹出文件保存对话框,否则浏览器会尝试直接显示文件。取值为:
  6.  
  7. attachment;filename="struts2.txt" 表示文件下载的时候保存的名字应为struts2.txt 。如果直接写filename="struts2.txt" ,那么默认情况是代表inline ,浏览器会尝试自动打开它,等价于这样的写法:inline; filename="struts2.txt"
  8.  
  9. bufferSize:下载缓冲区的大小

  测试结果:

  所以用来下载的action配置中,只有两个是和浏览器有关的:contentType 和contentDisposition 。关于contentType 的取值,如果是未知的文件类型,或者说出现了浏览器不能打开的文件,例如.bean 文件,或者说这个action是用来做动态文件下载的,事先并不知道未来的文件类型是什么,那么我们可以把它的值设置成为:application/octet-stream;charset=ISO8859-1 ,注意一定要加入charset ,否则某些时候会导致下载的文件出错;有人说这时也可以设置成为application/x-download ,根据笔者的实践,这个头也能正常工作,然而个别时候会出现浏览器无法识别的问题。而contentDisposition ,如果其取值是filename="struts2.txt" ,或者是inline; filename="struts2.txt" ,运行后你可以看到浏览器直接显示了文件的内容:

Struts 2 下载示例 ,而不再弹出对话框提示用户保存文件到硬盘上。所以读者如果想确保文件是被下载而不是被打开,务必使用格式attachment ;filename="struts2.txt" ,不要丢了attachment; 这个类型信息。

至此,关于文件下载的技术内容,已经告一段落。然而做中文系统,不可避免的要解决中文附件的下载问题。关于这个内容,也无权威的资料可查,我们只能用实践中得到的解决方案来处理。也许有读者以为将filename 属性设置为filename=”struts2 中文.txt” 就能解决问题了,好,就来试试,把contentDisposition修改成:

<param name="contentDisposition">attachment;filename="struts2 中文.txt"</param>

。再次键入地址进行测试,看看显示的结果,如图12.13所示。唉,真是完全不给面子!IE压根就不能显示出来文件名,草草敷衍了download_action了事。Firefox稍好点,还出来了一个对话框,但是很显然,那个显示的struts2--txt 绝对不是我们日思夜想的struts2 中文.txt 。怎么办?解决方法是有,那就是用ISO8859-1编码来显示这个中文字符,可以阅读12.8 参考资料 一节中的JSP 文件下载的相对完整代码( 解决中文问题和Weblogic 报错 )这篇文章,可以这样认为,所有的文件下载代码都是基于同样的纯Servlet的方式来进行的。如果是Java代码,我们可以这样做:

图12.13 IE和Firefox下的中文文件下载对话框

String downFileName = new String(“struts2 中文.txt”.getBytes(), "ISO8859-1");

  然后把生成的结果字符串放到XML文件中就行了,然而它的输出类似于struts2??.txt ,是无法直接写道我们的XML配置文件中的。所以,我们想到的的办法,就是在Action类中写一个方法来做转码,使它成为某个属性,所以要以get开头。然后,再用12.3.8 给Action 注入参数(param )值 一节的内容,将文件名以正常的方式设置为action类的某个属性,最后呢,再利用一个小小的param参数取值中的伎俩:${ 属性名} ,它可以直接从action类中动态获取某个属性值。好了,现在让我们来看看第二个文件下载类FileDownloadAction2 的代码:

  

  1. package example;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.InputStream;
  4. import java.io.UnsupportedEncodingException;
  5. import com.opensymphony.xwork2.Action;
  6. public class FileDownloadAction2 implements Action {
  7.  
  8. private String fileName;// 初始的通过param指定的文件名属性
  9. public InputStream getInputStream() throws Exception {
  10. return new ByteArrayInputStream("Struts 2 下载示例".getBytes());
  11. }
  12. public String execute() throws Exception {
  13.  
  14. return SUCCESS;
  15.  
  16. }
  17. public void setFileName(String fileName) {
  18. this.fileName = fileName;
  19. }
  20.  
  21. /** 提供转换编码后的供下载用的文件名 */
  22. public String getDownloadFileName() {
  23.  
  24. String downFileName = fileName;
  25. try {
  26. downFileName = new String(downFileName.getBytes(), "ISO8859-1");
  27. } catch (UnsupportedEncodingException e) {
  28. e.printStackTrace();
  29. }
  30. return downFileName;
  31. }
  32. }

   这个类有两个属性,第一个是fileName ,它是需要被指定的下载文件名;第二个则是动态的仅仅由getDownloadFileName() 这个方法定义的属性downloadFileName ,它的值随着fileName而动态变动,仅仅是把它转换成了ISO8859方式的西欧字符集。

接下来就是如何配置这个action了,这是关键的地方所在,现在配置一个新的action,名为download2 ,其源代码如下:

  1. <!-- 文件下载,支持中文附件名 -->
  2. <action name="download2" class="example.FileDownloadAction2">
  3. <!-- 初始文件名 -->
  4. <param name="fileName">Struts中文附件.txt</param>
  5. <result name="success" type="stream">
  6. <param name="contentType">text/plain</param>
  7. <param name="inputName">inputStream</param>
  8. <!-- 使用经过转码的文件名作为下载文件名,downloadFileName属性 对应action类中的方法 getDownloadFileName() -->
  9. <param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
  10. <param name="bufferSize">4096</param>
  11. </result>
  12. </action>

其中特殊的代码就是${downloadFileName} ,它的效果相当于运行的时候将action对象的属性的取值动态的填充在${} 中间的部分,我们可以认为它等价于 action. getDownloadFileName() 。

好了,现在让我们重新发布然后运行这个项目,键入地址:

http://localhost:8080/struts2hello/download2.action 进行访问,可以看到运行结果完全正确,如图12.14所示。

图 12.14 正确显示了文件下载名的对话框(IE和Firefox)

在本节的最后部分,我们来讨论一下如何下载已经存在于当前Web应用目录下的已经存在的文件。一般的网站可能会把要下载的文件放在某个固定的目录下,例如WebRoot/download ,在这个子目录下,我们放了一个名为系统说明 .doc 的文件,希望最后我们的action能够正确的下载这个文件。要检验下载是否成功非常简单,文件内容仅仅是粗体的系统说明书 这五个字,而word文件坏一个字节的话都是打不开的,所以下载后再用word打开即可检验是否成功。现在我们创建第三个文件下载的Action类,名为example. FileDownloadAction3 ,其源代码清单如下所示:

  1. package example;
  2. import java.io.InputStream;
  3. import java.io.UnsupportedEncodingException;
  4. import org.apache.struts2.ServletActionContext;
  5. import com.opensymphony.xwork2.Action;
  6. public class FileDownloadAction3 implements Action {
  7. private String fileName;// 初始的通过param指定的文件名属性
  8. private String inputPath;// 指定要被下载的文件路径
  9. public InputStream getInputStream() throws Exception {
  10. // 通过 ServletContext,也就是application 来读取数据
  11. return ServletActionContext.getServletContext().getResourceAsStream(inputPath);
  12. }
  13. public String execute() throws Exception {
  14. return SUCCESS;
  15. }
  16. public void setInputPath(String value) {
  17. inputPath = value;
  18. }
  19. public void setFileName(String fileName) {
  20. this.fileName = fileName;
  21. }
  22. /** 提供转换编码后的供下载用的文件名 */
  23. public String getDownloadFileName() {
  24. String downFileName = fileName;
  25. try {
  26. downFileName = new String(downFileName.getBytes(), "ISO8859-1");
  27. } catch (UnsupportedEncodingException e) {
  28. e.printStackTrace();
  29. }
  30. return downFileName;
  31. }
  32. }

代码中被改动的部分已经用粗斜体的方式显示出来了。首先是新加入了一个名为inputPath 的属性,用来制定被下载文件的路径。接着就是ServletActionContext.getServletContext() 这段代码,它的意义我们将在12.6 节详细讨论,在这里读者只需要知道它获取了当前Servlet容器的ServletContext ,也就是大家常说的jsp中的application 对象,然后用它来打开文件的输入流。

接着要做的就是配置action,它和刚刚配置过的download2的内容差不多,只是多了一个被下载的资源的路径属性。现在我们在struts.xml中加入这个新的action定义:

查看粗斜体的部分,首先就是自定了被下载文件的路径,inputPath,接着就是修改了contentType为二进制方式。最后重新发布项目并运行,键入地址进行访问:http://localhost:8080/struts2hello/download3.action 。很好,可以看到文件下载对话框,保存系统说明 .doc 后再用word打开它,内容正确。

注意: 而这种文件下载方式却是存在安全隐患的,因为访问者如果精通Struts 2的话,它可能使用这样的带有表单参数的地址来访问:http://localhost:8080/struts2hello/download3.action?inputPath=/WEB-INF/web.xml ,这样的结果就是下载后的文件内容是您系统里面的web.xml的文件的源代码,甚至还可以用这种方式来下载任何其它JSP文件的源码。这对系统安全是个很大的威胁。作为一种变通的方法,读者最好是从数据库中进行路径配置,然后把Action类中的设置inputPath的方法统统去掉,简言之就是删除这个方法定义:

public void setInputPath(String value) {

inputPath = value;

}

。而实际情况则应该成为 download3.action?fileid=1 类似于这样的形式来进行。或者呢,读者可以在execute()方法中进行路径检查,如果发现有访问不属于download下面文件的代码,就一律拒绝,不给他们返回文件内容。例如,我们可以把刚才类中的execute()方法加以改进,成为这样:

  

  1. public String execute() throws Exception {
  2. // 文件下载目录路径
  3. String downloadDir = ServletActionContext.getServletContext().getRealPath("/download");
  4. // 文件下载路径
  5. String downloadFile = ServletActionContext.getServletContext().getRealPath(inputPath);
  6. java.io.File file = new java.io.File(downloadFile);
  7. downloadFile = file.getCanonicalPath();// 真实文件路径,去掉里面的..等信息
  8. // 发现企图下载不在 /download 下的文件, 就显示空内容
  9. if(!downloadFile.startsWith(downloadDir)) {
  10. return null;
  11. }
  12. return SUCCESS;
  13. }

二、通过输出流对象将数据写到客户端,从而实现下载:

  1. //导出的文件可以使用 Excel 打开
  2. public void exportCSVFile() {
  3. PrintWriter pw = null;
  4. try {
  5. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
  6. servletResponse.setHeader("Content-type","application/octet-stream;charset=GBK");
  7. // 通过URLEncoder则文件名称不会出现乱码
  8. String filenameString = URLEncoder.encode("文件下载-", "utf-8")+ sdf.format(new Date());
  9. servletResponse.setHeader("Content-Disposition","attachment; filename=\"" + filenameString + ".csv\"");
  10. // map已有数据
  11. if (map != null && map.entrySet().size() > 0) {
  12. pw = servletResponse.getWriter();
  13. servletResponse.setCharacterEncoding("utf-8");
  14. pw.write("id,客户名称,客户编号\r\n");
  15. for (int i = 0; i < 10; i++) {
  16. pw.write("1001,名称,编号\r\n");
  17. }
  18. pw.close();
  19. }
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. } finally {
  23. if (pw != null) {
  24. try {
  25. pw.close();
  26. } catch (Exception e2) {
  27. }
  28. pw = null;
  29. }
  30. }
  31. }

三、 通过HTTP或者更底层的Socket实现

struts2 实现文件下载方法汇总的更多相关文章

  1. .net文件下载方法汇总

    转载自:http://blog.sina.com.cn/s/blog_680942070101ahsq.html //TransmitFile实现下载 protected void Button1_C ...

  2. ASP.NET导出excel表方法汇总

    asp.net里导出excel表方法汇总  1.由dataset生成 public void CreateExcel(DataSet ds,string typeid,string FileName) ...

  3. 关于Struts2的文件下载

    首先先来说下关于文件下载的原理: 服务端为客户端提供了一个下载服务,所以服务端需要一个输出流(把客户请求下载的文件输出),相对于服务端来说,客户端需要下载接收一个文件,所以它需要一个输入流(接收文件) ...

  4. 微擎系统BUG漏洞解决方法汇总(原创)

    微擎微赞系统BUG漏洞解决方法汇总 弄了微擎系统来玩玩,发觉这个系统BUG还不少,阿里云的提醒都一大堆,主要是没有针对SQL注入做预防,处理的办法基本都是用转义函数. 汇总: 1. 漏洞名称: 微擎任 ...

  5. Struts2笔记--文件下载

    Struts2提供了stream结果类型,该结果类型是专门用于支持文件下载功能的.配置stream类型的结果需要指定以下4个属性. contentType:指定被下载文件的文件类型 inputName ...

  6. 微擎系统BUG漏洞解决方法汇总

    微擎微赞系统BUG漏洞解决方法汇总 弄了微擎系统来玩玩,发觉这个系统BUG还不少,阿里云的提醒都一大堆,主要是没有针对SQL注入做预防,处理的办法基本都是用转义函数. 汇总: 1. 漏洞名称: 微擎任 ...

  7. 你真的会玩SQL吗?实用函数方法汇总

    你真的会玩SQL吗?系列目录 你真的会玩SQL吗?之逻辑查询处理阶段 你真的会玩SQL吗?和平大使 内连接.外连接 你真的会玩SQL吗?三范式.数据完整性 你真的会玩SQL吗?查询指定节点及其所有父节 ...

  8. Java实现时间动态显示方法汇总

    这篇文章主要介绍了Java实现时间动态显示方法汇总,很实用的功能,需要的朋友可以参考下 本文所述实例可以实现Java在界面上动态的显示时间.具体实现方法汇总如下: 1.方法一 用TimerTask: ...

  9. Struts2中动态方法的调用

    Struts2中动态方法调用就是为了解决一个action对应多个请求的处理,以免action太多. 主要有一下三种方法:指定method属性.感叹号方式和通配符方式.推荐使用第三种方式. 1.指定me ...

随机推荐

  1. 九度oj题目1511:从尾到头打印链表

    题目1511:从尾到头打印链表 时间限制:1 秒 内存限制:128 兆 特殊判题:否 提交:6010 解决:1805 题目描述: 输入一个链表,从尾到头打印链表每个节点的值. 输入: 每个输入文件仅包 ...

  2. centos6.5 源码编译 mysql5.6.21

    1.yum安装各个依赖包 [root@WebServer ~]# yum -y install gcc gcc-devel gcc-c++ gcc-c++-devel autoconf* automa ...

  3. VHDL

    数字逻辑VHDL 信号与变量 signal是全局的,在整个结构体中都有效,它的赋值是在进程结束, 也就是最后的赋值是有效的. variable是局部的,它的赋值是立即生效的. 一般变量是在进程的说明部 ...

  4. [转]Debugging into .NET Core源代码的两种方式

    本文转自:http://www.cnblogs.com/maxzhang1985/p/6015719.html 阅读目录 一.前言 二.符号服务器 三.项目中添加ASP.NET Core源代码 四.写 ...

  5. Jascript面向对象

    JavaScript 的核心是支持面向对象的,同时它也提供了强大灵活的 OOP 语言能力.本文从对面向对象编程的介绍开始,带您探索 JavaScript 的对象模型,最后描述 JavaScript 当 ...

  6. HDU 5012 骰子旋转(DFS)

    http://acm.hdu.edu.cn/showproblem.php?pid=5012 保存骰子的状态,然后用dfs或者bfs搜索 还是再讲一下dfs 我们的目标是找一个与b相同,且转次数最少的 ...

  7. 前端框架——Bootstrap

    一.Bootstrap介绍 凡是使用过bootstrap的开发者,不外乎做这么两件事情:复制and粘贴. Bootstrap官方网址:http://www.bootcss.com Bootstrap, ...

  8. LDAP入门与OpenLDAP使用配置

    LDAP入门与OpenLDAP使用配置 1.LDAP简介 LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务. ...

  9. Kafka监控利器

    开发过程中,kafka几乎是标配的Mq,如果有一个kafka的监控助手,哪就更完美了,常用的kafka监控工具有 KafkaOffsetMonitor .Kafka Manager.Capillary ...

  10. PLC-Heart