Struts2系统学习(10)文件上传与下载案例及原理分析
来源:互联网 发布:网络覆盖方案 编辑:程序博客网 时间:2024/06/10 05:57
#10. 文件上传与下载
10.1 文件上传
10.1.1 文件上传基本案例
第一步:上传组件依赖与commons-fileupload-1.3.1.jar和commons-io-2.2.jar。这两个文件可以从http://commons.apache.org/
下载或struts解压缩包中获取。
第二步:把form表单的enctype设置为:“multipart/form-data“,如下:
<form action="/Struts2Study/uploadFile.action" enctype="multipart/form-data" method="post"> 文件:<input name="uploadFile" type="file"><br> <input type="submit" value="上传文件"></form>
注:enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码。
默认地,表单数据会编码为 application/x-www-form-urlencoded
。就是说,在发送到服务器之前,所有字符都会进行编码(空格转换为 “+” 加号,特殊符号转换为 ASCII HEX 值)。
第三步:Action类中添加以下属性,表单中文件字段的名称name=”uploadFile”:
private File uploadFile; // 对应form表单中上传输入框的name值private String uploadFileContentType; // 上传文件类型ContentType固定写法private String uploadFileFileName; // 上传文件名FileName固定写法public String uploadFile() throws IOException { /* * struts2框架已经将上传的文件临时保存在uploadFile变量中,当action结束调用后, * 临时保存的文件就会删除,所以需要借助文件工具类保存到磁盘上! */ /* * 获取上下文(项目)下files目录的绝对路径。 * 考虑到:application.getRealPath(path)可以获取绝对路径 * 所以application -> ServletActionContext.getServletContext() */ String pathname = ServletActionContext.getServletContext().getRealPath("/files"); // 如果保存的路径不存在,则创建 File destDir = new File(pathname); if (!destDir.exists()) { destDir.mkdir(); } // destDir表示parentDir FileUtils.copyFile(uploadFile, new File(destDir, uploadFileFileName)); return "message";}
第四步:配置struts.multipart.saveDir常量
Struts2中的struts.multipart.saveDir常量主要是用来设置上传文件的临时存放地址,而这个参数设置方法的不同对应的地址也不同。 如果访问量不是太大,空间足够,可以直接使用默认的临时地址。
1.如果没有设置struts.multipart.saveDir,那么将默认使用javax.servlet.context.tempdir
指定的地址,javax.servlet.context.tempdir的值是由服务器来确定的,临时文件的名称类似于upload__1a156008_1373a8615dd__8000_00000001.tmp。
2.直接使用绝对地址(linux为例)
<constant name="struts.multipart.saveDir" value="/var/upload_temp_files"/>
第五步:设置可上传文件的大小限制
默认上传文件的最大值为 2097152字节,2M,如果上传的文件超过,则会报错。修改最大上传文件的大小:
<constant name="struts.multipart.maxSize" value=“10701096"/>
10.1.2 拦截器实现上传文件的过滤
Struts2提供类一个文件上传的拦截器fileUpload,通过配置该拦截器可以实现上传文件的过滤。打开该拦截器所对应的类FileUploadInterceptor:
public class FileUploadInterceptor extends AbstractInterceptor { protected Long maximumSize; protected Set<String> allowedTypesSet = Collections.emptySet(); protected Set<String> allowedExtensionsSet = Collections.emptySet(); ...}
可知配置fileUpload拦截器有3个参数:
- maximumSize:允许上传文件的大小限制,字节为单位
- allowedTypes:允许上传的文件类型,多个文件类型采用,分隔
- allowedExtensions:允许上传文件的后缀名
当文件过滤失败后,添加错误信息到系统fildError域,转入到input视图,因此该action需要提供input视图。为了保证拦截器可以添加错误信息,action需要实现ValidationAware接口,可以直接基础ActionSupport类。
(1)拦截器的配置如下:
<action name="uploadFile" class="com.markliu.day04.UploadFileAction" method="uploadFile"> <interceptor-ref name="fileUpload"> <param name="maximumSize">1024</param> <param name="allowedTypes">image/bmp,image/jpg,image/png</param> <param name="allowedExtensions">.bmp,.jpg,.png</param> </interceptor-ref> <interceptor-ref name="defaultStack" /> <result name="message">/pages/day04/message.jsp</result> <result name="input">/pages/day04/uploadfile.jsp</result></action>
注意:fileUpload拦截器必须在defaultStack(默认)拦截器前面配置,Struts2会由上到下执行拦截器。
(2)由于defaultStack拦截器栈中已经包含fileUpload拦截器,所以配置简化如下:
<interceptor-ref name="defaultStack"> <param name="fileUpload.maximumSize">1024</param> <param name="fileUpload.allowedTypes">image/bmp,image/jpg,image/png</param> <param name="fileUpload.allowedExtensions">.bmp,.jpg,.png</param> </interceptor-ref>
(3)修改显示国际化的错误信息:
在上传文件处理的action所在包下创建资源文件:一个是中文ActionName_zh_CN.properties、一个是英文ActionName_en_US.properties。对于中文资源文件,需要特别注意:我们应使用Myeclipse自带的MyEclipse properties Editer编辑器来打开此资源文件,并在properties视图下进行编辑,这样它会把中文进行编码(我们切换到source视图下可以看到经编码后的中文)。 这一步非常重要,否则会出现乱码。
可提供国际化资源文件的key值(FileUploadInterceptor中可查看)有:
struts.messages.error.uploading - a general error that occurs when the file could not be uploadedstruts.messages.error.file.too.large - occurs when the uploaded file is too largestruts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected content types specifiedstruts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected file extensions specified
10.2 文件下载
10.2.1 文件下载案例解析
下载最终是根据contentType和数据流将数据输出到客户端来实现。下面将直接通过案例讲解(解决了下载的文件名中文乱码问题):
(1)处理文件下载的action:
package com.markliu.day08;import java.io.InputStream;import java.io.UnsupportedEncodingException;import org.apache.struts2.ServletActionContext;import com.opensymphony.xwork2.ActionSupport;public class FileDownloadAction extends ActionSupport { private static final long serialVersionUID = -4320979410140144964L; private String fileName; public String getFileName() throws UnsupportedEncodingException { /* * HTTP协议将响应按照ISO-8859-1编码格式进行转换之后再传递。 * 因此,为防止客户端显示下载的文件名出现中文乱码,需要将fileName 从UTF-8 --> ISO-8859-1 */ String name = new String(fileName.getBytes("UTF-8"), "ISO-8859-1"); return name; } public void setFileName(String fileName) throws UnsupportedEncodingException { String name = new String(fileName.getBytes("ISO-8859-1"), "UTF-8"); this.fileName = name; } /* * 返回一个输入流,作为一个客户端来说为输入流,但对于服务器端为输出流 */ public InputStream getDownloadFileAsInputStream() throws Exception { // 测试<param name="inputName">配置成功 System.out.println("getDownloadFileAsInputStream"); // 根据资源路径获取输入流,注意此处的fileName仍让是UTF-8编码 InputStream in = ServletActionContext.getServletContext().getResourceAsStream("/files/"+fileName); // 测试文件是否存在 System.out.println(in); return in; }}
(2)struts.xml配置:
<package name="day08" namespace="/day08" extends="struts-default"> <action name="fileDownload" class="com.markliu.day08.FileDownloadAction" method="execute"> <result name="success" type="stream"> <!-- 设置contentType类型,并设置响应的编码类型为UTF-8 --> <param name="contentType">application/x-msdownload;charset=UTF-8</param> <param name="contentDisposition">attachment;fileName="${fileName}"</param> <param name="inputName">downloadFileAsInputStream</param> <param name="bufferSize">1024</param> </result> </action> </package>
(3)下载页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body> <a href="/Struts2Study/day08/fileDownload.action?fileName=马克.jpg">下载文件</a></body></html>
代码讲解:
struts.xml的配置中<result name="success" type="stream">
结果类型必须要写成type="stream"
,查看struts2-default.xml可知,其对应的类为StreamResult:
<result-types> <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/></result-types>
打开StreamResult的源代码:
public class StreamResult extends StrutsResultSupport { public static final String DEFAULT_PARAM = "inputName"; protected String contentType = "text/plain"; protected String contentLength; protected String contentDisposition = "inline"; protected String contentCharSet ; protected String inputName = "inputStream"; protected InputStream inputStream; protected int bufferSize = 1024; protected boolean allowCaching = true; ...}
当然这些参数都可以在<result type="stream">
中配置 例如:
<!-- 指定下载文件的文件类型,默认为text/plain --><param name="contentType">Application/pdf</param><!-- 指定由getDownloadFileAsInputStream()方法返回被下载文件的InputStream --><param name="inputName">downloadFileAsInputStream</param><!-- 下载对话框中显示下载的文件的名称 --><param name="contentDisposition">attachment;fileName="${fileName}"</param><!-- 指定下载文件的缓冲大小 --><param name="bufferSize">2048</param><!-- 是否支持缓存,默认为true --><param name="allowCaching">true</param>
注意:
1. contentDisposition:服务端向客户端浏览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器中显示,如果需要提示用户保存,就要利用Content-Disposition进行一下处理,关键在于一定要加上attachment。指定下载的文件名和显示方式,一般文件名和原文件名一致,但是要注意中文名保存时乱码问题,解决办法就是进行编码处理。
2. attachment :下载时会打开下载框
3. fileName=”${fileName}” :在这定义的名字是一个动态的,该名字是显示在下载框上的文件名字
4.<param name="inputName">downloadFileAsInputStream</param>
,这个downloadFileAsInputStream名字要和action中的getDownloadFileAsInputStream()方法名去掉get一致
10.2.2 文件名中文乱码问题
文件名中文乱码问题:
文件名中文乱码会出现文件找不到错误而无法下载,或导致下载的文件名乱码。
解决思路:本案例中请求页面为UTF-8格式,即<a href="/Struts2Study/day08/fileDownload.action?fileName=马克.jpg">下载文件</a>
中的文件名会按照UTF-8的格式编码,但是HTTP协议将请求按照ISO-8859-1编码格式进行转换之后再传递。也就是说fileName又进行了ISO-8859-1编码,服务器端request的编码格式为UTF-8(由上面测试可知),所以按照上面编码的逆过程,setFileName中注入的fileName为UTF-8格式,服务器发送响应之前也将其ISO-8859-1编码,所以发送之前需要手动为fileName编码:String name = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
客户端则逆过程ISO-8859-1解码 – UTF-8解码,显示正确的中文(代码见案例)。
10.2.3 Can not find a java.io.InputStream with the name [downloadFileAsInputStream] in the invocation stack问题解决
具体异常是这句话:
Can not find a java.io.InputStream with the name [downloadFileAsInputStream] in the invocation stack. Check the <param name="inputName"> tag specified for this action.
测试发现原因可能有如下几点:
1. 文件路径不对。
ServletActionContext.getServletContext().getResourceAsStream(“…”)这种方法获得输入流异常。要保证文件位置在ServletContext当中,或直接使用绝对路径获取输入流。
2. 在action的配置<param name="inputName">
错误。
排除错误原因的测试方法是在<param name="inputName">
指定的方法中进行测试:
public InputStream getDownloadFileAsInputStream() throws Exception { // 测试<param name="inputName">配置成功 System.out.println("getDownloadFileAsInputStream"); // 根据资源路径获取输入流,注意此处的fileName仍让是UTF-8编码 InputStream in = ServletActionContext.getServletContext().getResourceAsStream("/files/"+fileName); // 测试文件是否存在,不存在则in为null System.out.println(in); return in;}
10.2.4 Struts2下载文件的核心类StreamResult
Struts2下载文件的核心类为org.apache.struts2.dispatcher.StreamResult,下面主要分析其中的doExecute方法:
/*所要下载文件锁对应的输入流*/protected InputStream inputStream;/** * @param invocation: ActionInvocation代表Action的执行状态,其中包含了锁对应的拦截器和action实例本身。 */ protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { /* * 根据设置的参数值(contentDisposition,contentType,inputName等)覆盖类中默认的值,类似与下面的代码 * String inputName = stack.findString("inputName"); * if (inputName != null) { * setInputName(inputName); // 此处设置了action中获取inputstream的方法 * } * ... */ resolveParamsFromStack(invocation.getStack(), invocation); // 向浏览器输出流 OutputStream oOutput = null; try { if (inputStream == null) { // Find the inputstream from the invocation variable stack inputStream = (InputStream) invocation.getStack().findValue(conditionalParse(inputName, invocation)); } if (inputStream == null) { String msg = ("Can not find a java.io.InputStream with the name [" + inputName + "] in the invocation stack. " + "Check the <param name=\"inputName\"> tag specified for this action."); LOG.error(msg); throw new IllegalArgumentException(msg); } // 从该action的OGNL上下文ActionContext中获取对应的响应对象 HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE); /* * 此处省略了设置响应的content-type,contentLength,bufferSize,contentDisposition等 */ ... // 以下实现了文件的发送! oOutput = oResponse.getOutputStream(); byte[] oBuff = new byte[bufferSize]; int iSize; while (-1 != (iSize = inputStream.read(oBuff))) { oOutput.write(oBuff, 0, iSize); } // Flush oOutput.flush(); } finally { if (inputStream != null) inputStream.close(); if (oOutput != null) oOutput.close(); } }
转载请注明出处:http://blog.csdn.net/mark_lq/article/details/49864361
- Struts2系统学习(10)文件上传与下载案例及原理分析
- Struts2的文件上传与下载案例
- 文件上传与下载学习笔记(1)---文件上传原理及配置
- struts2 文件上传与下载原理
- struts2 文件上传与下载原理
- struts2 文件上传与下载原理
- struts2 文件上传与下载原理
- struts2学习笔记(六)文件上传与下载(下)基于Struts2的文件上传与下载
- 文件上传与下载(一)struts2
- Struts2文件上传与下载
- Struts2文件上传与下载
- Struts2文件上传与下载
- Struts2文件上传与下载
- Struts2 文件上传与下载
- struts2文件上传与下载
- Struts2文件上传与下载
- Struts2文件上传与下载
- Struts2文件上传与下载
- 配置RHEL6使用CentOS6的yum源
- 由S_ISDIR学到的
- Android总结篇系列:Android Service
- 旋转数组的最小数字
- Java反射reflect学习笔记_2:反射类的构造函数
- Struts2系统学习(10)文件上传与下载案例及原理分析
- HttpClient使用详解
- MD5杂凑算法
- Java输出特定时间段特定格式时间信息
- Android View事件分发处理
- prime算法-最小生成树算法
- iOS8 自定义UITabBar (使用popToViewController导致的UITabBarButton重叠的问题)
- 定时任务——表达式
- mysql .ibd .frm