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 值)。

属性值 值描述 application/x-www-form-urlencoded 在发送前编码所有字符(默认) multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。 text/plain 空格转换为 “+” 加号,但不对特殊字符编码。


  第三步: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

1 0