您的位置:首页 > 编程语言 > Java开发

Struts2文件上传(2)_细节

2012-06-19 12:58 274 查看
一.Struts2文件上传实现细节简介
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的实例中。
也就是配置文件的 <interceptor-ref name="fileUpload"/>

我们可以通过源代码struts2-code-XX.jar的struts-default.xml文件找到

<interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>

三.拦截器的实现细
1.拦截器的注释

//注释
org.apache.struts2.interceptor.FileUploadInterceptor

Interceptor that is based off of 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: 

//此拦截器基于MultiPartRequestWrapper的,后者在请求中包含文件时自动运行。
//他在上下文中添加了以下参数

[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) //文件的实际名

You can get access to these files by merely providing setters in your action that correspond to any of the three patterns above, such as setDocument(File document), setDocumentContentType(String contentType), etc. 
//你可以通过提供setter方法来访问这些file

See the example code section. 

This interceptor will add several field errors, assuming that the action implements ValidationAware. These error messages are based on several i18n values stored in struts-messages.properties, a default i18n file processed for all i18n requests. You can override the text of these messages by providing text for the following keys: 
//这个拦截器将添加多个域的错误,假设Action实现ValidationAware接口。
//这些错误信息基于Struts2的i18n资源文件中。(比如struts-messages.properties)
//您可以覆盖这些消息的文本

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 

Interceptor parameters: 
//拦截器参数

//文件最大
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. 

//扩展这个拦截器
Extending the interceptor: 

You can extend this interceptor and override the acceptFile method to provide more control over which files are supported and which are not. 
//你可以扩展这个拦截器和覆盖accepttFile方法来提供支持那些文件的控制权

Example code: 
//示例代码

 
 <action name="doUpload" class="com.example.UploadAction">
     <interceptor-ref name="fileUpload"/>
     <interceptor-ref name="basicStack"/>
     <result name="success">good_result.jsp</result>
 </action>
 
 
//设置表单属性
You must set the encoding to multipart/form-data in the form where the user selects the file to upload. 

 
   <s:form action="doUpload" method="post" enctype="multipart/form-data">
       <s:file name="upload" label="File"/>
       <s:submit/>
   </s:form>
 
 
And then in your action code you'll have access to the File object if you provide setters according to the naming convention documented in the start. 
//如果你在Action代码中提供setter方法,那么你将获得文件的访问权

 
    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;
       }
  }




2.拦截器的拦截方法

//[拦截器的拦截方法]
	public String intercept(ActionInvocation invocation) throws Exception {
		
		//[获取ActionContext]
        ActionContext ac = invocation.getInvocationContext();
	
		//[获取HttpServletRequest]
        HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);

		//[如果这个请求不是一个二进制数据流]
        if (!(request instanceof MultiPartRequestWrapper)) {
			//[如果日志可用debug,打印日志]
            if (LOG.isDebugEnabled()) {
                ActionProxy proxy = invocation.getProxy();
                LOG.debug(getTextMessage("struts.messages.bypass.request", new Object[]{proxy.getNamespace(), proxy.getActionName()}, ac.getLocale()));
            }
			//[继续下一个拦截器,或者进入Action]
            return invocation.invoke();
        }

		//[声明校验感知器变量]
        ValidationAware validation = null;

		//[获取Action]
        Object action = invocation.getAction();

		//[如果这个Action是一个校验感知器]
        if (action instanceof ValidationAware) {
			//[将Action强制转换为校验感知器]
            validation = (ValidationAware) action;
        }

		//[将HttpServletRequest强制转换为MultiPartRequestWrapper]
        MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;

		//[如果这个请求包装类含有错误]
        if (multiWrapper.hasErrors()) {
            for (String error : multiWrapper.getErrors()) {
                if (validation != null) {
                    validation.addActionError(error);
                }

                if (LOG.isWarnEnabled()) {
                    LOG.warn(error);
                }
            }
        }

        // bind allowed Files
		//[从请求包装获取文件域的名字]
        Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
		//[当文件域名字不为空,并且含有多个参数的时候进入循环]
        while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
            // get the value of this input tag
			//[得到下一个文件域的name属性值]
            String inputName = (String) fileParameterNames.nextElement();

            // get the content type
			//[得到这个请求中的mime文件类型数组]
            String[] contentType = multiWrapper.getContentTypes(inputName);
		
			//[如果这个上下文类型数组非空]
            if (isNonEmpty(contentType)) {
                // get the name of the file from the input tag
				//[得到文件名数组]
                String[] fileName = multiWrapper.getFileNames(inputName);
					
				//如果文件名数组是非空的(有文件被选择)
                if (isNonEmpty(fileName)) {
                    // get a File object for the uploaded File
					//[得到文件数组]
                    File[] files = multiWrapper.getFiles(inputName);
					//[如果文件数组不为空]
                    if (files != null && files.length > 0) {
                        //[初始化文件、文件上下文类型、文件名列表]
						List<File> acceptedFiles = new ArrayList<File>(files.length);
                        List<String> acceptedContentTypes = new ArrayList<String>(files.length);
                        List<String> acceptedFileNames = new ArrayList<String>(files.length);
						
                        String contentTypeName = inputName + "ContentType";
                        String fileNameName = inputName + "FileName";
						
						//[遍历这个文件数组]
                        for (int index = 0; index < files.length; index++) {
                            if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation, ac.getLocale())) {
								//[把文件、文件上下文类型、文件名加入列表]
                                acceptedFiles.add(files[index]);
                                acceptedContentTypes.add(contentType[index]);
                                acceptedFileNames.add(fileName[index]);
                            }
                        }
						
						//[如果接收的文件类表不为空]
                        if (!acceptedFiles.isEmpty()) {
							//将文件,文件类型,文件名放到上下文参数中注入
                            Map<String, Object> params = ac.getParameters();

                            params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
                            params.put(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
                            params.put(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
                        }
                    }
                } else {
                    if (LOG.isWarnEnabled()) {
                	LOG.warn(getTextMessage(action, "struts.messages.invalid.file", new Object[]{inputName}, ac.getLocale()));
                    }
                }
            } else {
                if (LOG.isWarnEnabled()) {
                    LOG.warn(getTextMessage(action, "struts.messages.invalid.content.type", new Object[]{inputName}, ac.getLocale()));
                }
            }
        }

        // invoke action
		// 继续下一个拦截器
        return invocation.invoke();
    }


三.一般上传文件的原理:
具体可以看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 + "<br />");

}

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();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: