struts2 文件上传和下载,以及部分源码解析

作者:风风 发布于:2010年06月14日 14:42

struts2 文件上传 和部分源码解析,以及一般上传原理

(1) 单文件上传

一.简介

Struts2并未提供自己的请求解析器,也就是就Struts2不会自己去处理multipart/form-data的请求,它需要调用其他请求解析器,将HTTP请求中的表单域解析出来。但Struts2在原有的上传解析器基础

上做了进一步封装,更进一步简化了文件上传。
Struts2默认使用的是Jakarta的Common-FileUpload框架来上传文件,因此,要在web应用中增加两个Jar文件:commons-fileupload-1.2.jar和commons-io-1.3.1.jar。它在原上传框架上做了进一步封装

,简化了文件上传的代码实现,取消了不同上传框架上的编程差异。
如果要改成其它的文件上传框架,可以修改struts.multipart.parser常量的值为cos/pell,默认值是jakata。并在classpath中增加相应上传组件的类库

例如配置成cos上传

struts.multipart.parser=cos

struts.multipart.maxSize=1024 指定文件的最大字结数

二.原理

不管用common-fileUPload框架,还是用cos,都是通过将HTTP的数据保存到临时文件夹,然后Struts使用fileUpload拦截器将文件绑定到Action的实例中。
也就是配置文件的
我们可以通过源代码struts2-code-XX.jar的struts-default.xml文件找到

打开这个类的源代码可以看见相关如下:
/**
*
*

* Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
* includes a file. It adds the following parameters, where [File Name] is the name given to the file uploaded by the
* HTML form:
*

*

    *

    *

  • [File Name] : File - the actual File
  • *

    *

  • [File Name]ContentType : String - the content type of the file
  • *

    *

  • [File Name]FileName : String - the actual name of the file uploaded (not the HTML name)
  • *

    *

*

也就是说我们需要三个变量File(表单的name),其他两个参数通过set个体方法有strtus调用

接着下面是一些国际化提示的东西:
* processed for all i18n requests. You can override the text of these messages by providing text for the following
* keys:
*

  • struts.messages.error.uploading - a general error that occurs when the file could not be uploaded
  • *

    *

  • struts.messages.error.file.too.large - occurs when the uploaded file is too large
  • *

    *

  • struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
    * content types specified
  • *

    *

  • struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected
    * file extensions specified
  • *

    例如struts.messages.error.content.type.not.allowed 表示文件类型错误:也就是说如果我们给拦截器配置了属性allowedTypes 例如:
    image/bmp,image/png,image/gif,image/jpeg,image/jpg 但是上传的时候没有上传规定的类型
    struts2就会去我们的资源文件去找key为struts.messages.error.content.type.not.allowed的国际化资源给与提示这时候我们可以在我们的资源中配置这个key:
    例如:struts.messages.error.content.type.not.allowed=您上传的文件类型只能为…!请重新选择!
    (当然需要)globalMessages为资源前缀,然后通过:来显示提示

    *

    *

      *

      *

    • maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
      * on the action. Note, this is not related to the various properties found in struts.properties.
      * Default to approximately 2MB.
    • *

      *

    • allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
      * a file reference to be set on the action. If none is specified allow all types to be uploaded.
    • *

      *

    • allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
      * a file reference to be set on the action. If none is specified allow all extensions to be uploaded.
    • *

    *

    上面则是拦截器的相关参数,一目了然:maximumSize 上传文件最大多少 默认:2MB。allowedTypes容许的上传类型。allowedExtensions容许的扩展名
    接着是相关action的代码说明:

    * package com.example;
    *

    * import java.io.File;
    * import com.opensymphony.xwork2.ActionSupport;
    *

    * public UploadAction extends ActionSupport {
    * private File file;
    * private String contentType;
    * private String filename;
    *

    * public void setUpload(File file) {
    * this.file = file;
    * }
    *

    * public void setUploadContentType(String contentType) {
    * this.contentType = contentType;
    * }
    *

    * public void setUploadFileName(String filename) {
    * this.filename = filename;
    * }
    *

    * public String execute() {
    * //…
    * return SUCCESS;
    * }
    * }
    其实最主要的是set方法的确定:我们跟踪到大约238行:
    String contentTypeName = inputName + “ContentType”;
    String fileNameName = inputName + “FileName”;
    最终确定我们的private File file;属性名称可以随便,
    但是filenam和contenttype的set方法要有规定 例如:
    如果private File myFile;

    则对应的其他的两个属性set方法如下:

    public void setMyFileContentType(String contentType) {
    this.contentType = contentType;//当然contentType可以随便起名 最终要的是set+MyFile+ContentType方法
    }

    public void setMyFileFileName(String filename) {
    this.filename = filename;/当然filename可以随便起名 最终要的是set+MyFile+FileName方法
    }
    以下是实例:
    三.需要的jar包(默认使用commons-fileupload,如果使用cos,要将jar引进来)

    commons-logging-1.1.jar
    freemarker-2.3.8.jar
    ognl-2.6.11.jar
    struts2-core-2.0.6.jar
    xwork-2.0.1.jar
    commons-io-1.3.1.jar
    commons-fileupload-1.2.jar

    四.实例

    1.首先,创建上传页面

    Html代码
    1.
    2.
    3.
    4.
    5.
    6.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    26.

    27.
    28.
    29.

    2.action

    1.package com;
    2.
    3.import java.io.BufferedInputStream;
    4.import java.io.BufferedOutputStream;
    5.import java.io.File;
    6.import java.io.FileInputStream;
    7.import java.io.FileOutputStream;
    8.import java.io.InputStream;
    9.import java.io.OutputStream;
    10.import java.util.Date;
    11.
    12.import org.apache.struts2.ServletActionContext;
    13.
    14.import com.opensymphony.xwork2.ActionSupport;
    15.
    16.public class FileUploadAction extends ActionSupport {
    17.
    18. private static final long serialVersionUID = 6452146812454l;
    19.
    20. private File upload;
    21.
    22. private String uploadContentType;
    23.
    24. private String uploadFileName;
    25.
    26. private String imageFileName;
    27.
    28. public String getUploadContentType() {
    29. return uploadContentType;
    30. }
    31.
    32. public void setUploadContentType(String uploadContentType) {
    33. this.uploadContentType = uploadContentType;
    34. }
    35.
    36. public File getUpload() {
    37. return upload;
    38. }
    39.
    40. public void setUpload(File upload) {
    41. this.upload = upload;
    42. }
    43.
    44. public String getUploadFileName() {
    45. return uploadFileName;
    46. }
    47.
    48. public void setUploadFileName(String uploadFileName) {
    49. this.uploadFileName = uploadFileName;
    50. }
    51.
    52. public void setImageFileName(String imageFileName) {
    53. this.imageFileName = imageFileName;
    54. }
    55.
    56. public String getImageFileName() {
    57. return imageFileName;
    58. }
    59.
    60. private static void copy(File src, File dst) {
    61. try {
    62. InputStream in = null;
    63. OutputStream out = null;
    64. try {
    65. in = new BufferedInputStream(new FileInputStream(src));
    66. out = new BufferedOutputStream(new FileOutputStream(dst));
    67. byte[] buffer = new byte[1024*10];
    68. while (in.read(buffer) > 0) {
    69. out.write(buffer);
    70. }
    71. } finally {
    72. if (null != in) {
    73. in.close();
    74. }
    75. if (null != out) {
    76. out.close();
    77. }
    78. }
    79. } catch (Exception e) {
    80. e.printStackTrace();
    81. }
    82. }
    83.
    84. @Override
    85. public String execute() {
    86. System.out.println(uploadFileName);
    87.
    88. imageFileName = System.currentTimeMillis() + uploadFileName.substring(uploadFileName.lastIndexOf(”.”));
    89. File imageFile = new File(ServletActionContext.getServletContext()
    90. .getRealPath(”/uploadImages”)
    91. + “/” + imageFileName); //我们自己重新定义的文件名,也可以直接用 uploadFileName
    92. copy(upload, imageFile);
    93. return SUCCESS;
    94. }
    95.
    96.}
    97
    表单的enctype =”multipart/form-data,与一般的上传一样.

    会将upload绑定到action的upload,其次他还会将上传记文件的MIME类型绑定到uploadContentType,文件名绑定到uploadFileName中,他们是通过

    setUploadContentType和setUploadFileName进行绑定的,下面进行的多文件上传也是同个道理,不过要用数组或者是list来进行绑定,然后多个文件的MIME类型也会绑定到以数组

    名字加ContentType和FileName的字符串数组中。 比如说上传的文件的数组名为:File[] uploads,则它们的MIME类型绑定的对应的数组是uploadsFileName和uploadsContentType.

    3.struts.xml的配置

    Xml代码

    Xml代码
    1.
    2.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12. image/bmp,image/png,image/gif,image/jpeg,image/jpg
    13.
    14.
    15.
    16. /fileUpload.jsp
    17. /showUpload.jsp
    18.
    19.
    20.

    4.最后是web.xml的配置

    Xml代码
    1.
    2.
    7.
    8.
    9. struts-cleanup
    10.
    11. org.apache.struts2.dispatcher.ActionContextCleanUp
    12.
    13.
    14.
    15.
    16. struts2
    17.
    18. org.apache.struts2.dispatcher.FilterDispatcher
    19.
    20.
    21.
    22.
    23. struts-cleanup
    24. /*
    25.
    26.
    27.
    28. struts2
    29. /*
    30.
    31.
    32.
    33. index.jsp
    34.
    35.

    (2) 多文件上传

    多文件上传
    与单文件上传相似,实现多文件你可以将多个绑定Action的数组或列表。如下例所示。






    清单14 多文件上传JSP代码片段
    如果你希望绑定到数组,Action的代码应类似:

    private File[] uploads;
    private String[] uploadSFileName;
    private String[] uploadSContentType;

    多文件上传数组绑定Action代码片段
    如果你想绑定到列表,则应类似:

    private List uploads ;
    private List uploadSFileName ;
    private List uploadSContentType ;
    多文件上传列表绑定Action代码片段

    另外是一般上传文件的原理:当然具体可以看http协议的rfc文档:
    关于multipart/form-data 相关资料可以看;http://www.ietf.org/rfc/rfc1867.txt 大约在[Page 1]的地方有介绍
    表单配置multipart/form-data 说明以二进制流的方式传输表单字段的数据:
    我们通过以下代码看到request数据流中的内容:

    PrintWriter out = response.getWriter();
    InputStream is = request.getInputStream();

    BufferedReader br = new BufferedReader(
    new InputStreamReader(is));
    String buffer = null;
    while( (buffer = br.readLine()) != null)
    {
    //在页面中显示读取到的请求参数
    out.println(buffer + “
    “);
    }
    out.flush();
    out.close();
    例如:我上传一个文件D:\apache-tomcat-6018\bin\version.sh (tomcat版本文件)
    最终页面显示:
    —————————–7da1052ec05fe
    Content-Disposition: form-data; name=”ff”; filename=”D:\apache-tomcat-6018\bin\version.sh”
    Content-Type: text/plain

    #!/bin/sh

    # Licensed to the Apache Software Foundation (ASF) under one or more
    # contributor license agreements. See the NOTICE file distributed with
    # this work for additional information regarding copyright ownership.
    # The ASF licenses this file to You under the Apache License, Version 2.0
    # (the “License”); you may not use this file except in compliance with
    # the License. You may obtain a copy of the License at
    #
    # http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an “AS IS” BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    # resolve links - $0 may be a softlink
    PRG=”$0″

    while [ -h "$PRG" ] ; do
    ls=`ls -ld “$PRG”`
    link=`expr “$ls” : ‘.*-> \(.*\)$’`
    if expr “$link” : ‘/.*’ > /dev/null; then
    PRG=”$link”
    else
    PRG=`dirname “$PRG”`/”$link”
    fi
    done

    PRGDIR=`dirname “$PRG”`
    EXECUTABLE=catalina.sh

    # Check that target executable exists
    if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
    echo “Cannot find $PRGDIR/$EXECUTABLE”
    echo “This file is needed to run this program”
    exit 1
    fi

    exec “$PRGDIR”/”$EXECUTABLE” version “$@”

    —————————–7da1052ec05fe–
    我们发现我们上传的内容在
    —————————–7da1052ec05fe
    Content-Disposition: form-data; name=”ff”; filename=”D:\apache-tomcat-6018\bin\version.sh”
    Content-Type: text/plain和—————————–7da1052ec05fe–中间
    因此我们可以通过以下代码来获取上传内容并保存:

    //取得HttpServletRequest的InputStream输入流
    InputStream is = request.getInputStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    String buffer = null;
    //循环读取请求内容的每一行内容
    while( (buffer = br.readLine()) != null)
    {
    //如果读到的内容以—————————–开始,
    //且以–结束,表明已到请求内容尾
    if(buffer.endsWith(”–”) && buffer
    .startsWith(”—————————–”))//length为29
    {
    //跳出循环
    break;
    }
    //如果读到的内容以—————————–开始,表明开始了一个表单域
    if(buffer.startsWith(”—————————–”))
    {
    //如果下一行内容中有filename字符串,表明这是一个文件域
    if (br.readLine().indexOf(”filename”) > 1)
    {
    //跳过两行,开始处理上传的文件内容
    br.readLine();
    br.readLine();
    //以系统时间为文件名,创建一个新文件
    File file = new File(request.getRealPath(”/”)
    + System.currentTimeMillis());
    //当然我们可以读取filenam来保存这里简化
    //创建一个文件输出流
    PrintStream ps = new PrintStream(new FileOutputStream(file));
    String content = null;
    //接着开始读取文件内容
    while( (content = br.readLine()) != null)
    {
    //如果读取的内容以—————————–开始,
    //表明开始了下一个表单域内容
    if(content.startsWith(”—————————–”))length为29
    {
    //跳出处理
    break;
    }
    //将读到的内容输出到文件中
    ps.println(content);
    }
    //关闭输出
    ps.flush();
    ps.close();
    }
    }
    }
    br.close();

    关于strtus2下载:
    下载最终是通过contentType和数据流将数据输出到客户端来实现,在struts中也是通过InputStream和相关的配置来实现:
    同样最终到strtus的下载相关的源代码:org.apache.struts2.dispatcher.StreamResult我们看到

    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;

    当然这些参数都可以在 中配置 例如;
    \uploads\document.pdf

    Application/pdf

    targetFile
    attachment;filename=”document.pdf”

    2048
    其中:
    contentType:指定被下载文件的文件类型。 application/octet-stream 默认值,可以下载所有类型
    inputName:指定被下载文件的入口输入流, 和DownloadAction中的getInputStream()对应,主要是获得实际资源文件
    contentDisposition:指定下载的文件名和显示方式,一般和文件名一致,但是要注意中文件名保存时乱码问题,解决办法就是进行编码处理

    targetFile
    是下载的入口 我们不需要在我们的action里面配置targetFile变量 但需要getTargetFile方法,默认需要getInputStream()方法 也就是:inputName参数的值就是入口方法去掉get前缀、首字母小写的

    字符串

    我们的action里面的代码如下:

    private String inputPath;//通过strtus获取文件地址 也可以直接写例如:String inputPath = ServletActionContext.getRequest().getRealPath(”\uploads\document.pdf”);

    public void setInputPath(String value)
    {
    inputPath = value;
    }
    public InputStream getTargetFile() throws Exception
    {
    return ServletActionContext.getServletContext().getResourceAsStream(inputPath);
    }
    如果报以下错误:
    Can not find a java.io.InputStream with the name [targetFile] in the invocation stack. Check the tag specified for this action.

    实际问题是ServletActionContext.getServletContext().getResourceAsStream(inputPath);找不到资源,请检查你的path是否正确。
    而关于下载实际struts做了什么呢?我们看一部分源代码代码就很明白了:

    HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
    // Set the content type

    //Set the content length

    // Set the content-disposition

    // Set the cache control headers if neccessary

    // Get the outputstream
    //————
    oOutput = oResponse.getOutputStream();

    if (LOG.isDebugEnabled()) {
    LOG.debug(”Streaming result [" + inputName + "] type=[" + contentType + "] length=[" + contentLength +
    "] content-disposition=[" + contentDisposition + "] charset=[" + contentCharSet + "]“);
    }

    // Copy input to output
    LOG.debug(”Streaming to output buffer +++ START +++”);
    byte[] oBuff = new byte[bufferSize];
    int iSize;
    while (-1 != (iSize = inputStream.read(oBuff))) {
    oOutput.write(oBuff, 0, iSize);
    }
    LOG.debug(”Streaming to output buffer +++ END +++”);

    // Flush
    oOutput.flush();
    }
    finally {
    if (inputStream != null) inputStream.close();
    if (oOutput != null) oOutput.close();
    }
    //———–
    很简单,就像以前在servlet中一样通过getOutputStream 和配置content type ,content-disposition,cache control,content length这些参数的来实现。

    这样就很简单的实现了下载功能。
    以上是自己工作之余写的以下总结,不对的地方希望大家指点,谢谢,转载,请说明地址

    作者:风风
    来源:北风技术专栏
    原文链接:http://column.ibeifeng.com/allg0/20100614310.shtml

    ( 内容完 )

    添加收藏到:

    您可能还对这些文章感兴趣:

  • Hibernate+Struts的J2EE应用开发
  • 怎样能学好Java
  • 给学习java的应届生找工作的一些建议
  • 博客系统第一讲:课程介绍和系统分析
  • Spring集成XFire开发WebService
  • 友情链接
  • ewebeditor使用教程(一)
  • 不好驯服的析构函数
  • 用Flex+Lucene+s2sh开发订餐系统开发第三步:前台主界面设计(续)笔记分享
  • SQL优化笔记
  • 没有评论, 我来评论

    小贴士:评论需要管理员审核后才会显示。请不要发布与国家法律相抵触的言论,北风网将保留追究责任的权利。
    类似“顶”、“沙发”、“支持”之类没有营养的文字,对勤劳贡献的作者来说是令人沮丧的反馈信息。
    请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。
    如果您发现自己的评论没有被审核或者不见了,请参考以上三条。

    

    每周之星

    陈臣陈臣

    七年Java和JEE开发经验,JEE应用设计和高级架构师,拥有Sun的多项Java和J2EE方面的技能认证,多年项目经理、技术部经理的管理经验。拥有全面、扎实的Java和JEE理论知识,丰富的JEE应用开发经验。

    更多作者:

  • Adam
  • ikon999
  • jk1234
  • jk2345
  • libin_8745
  • lifengxing
  • taohuang100
  • xingkong
  • 北风
  • 呆子
  • 子晨
  • 小白
  • 张章
  • 张维亮
  • 陈臣
  • 陶宝哥
  • 风风
  • 最新内容

    推荐内容

    标签

    分类