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

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

2014-04-21 10:53 666 查看
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这些参数的来实现。

这样就很简单的实现了下载功能。

以上是自己工作之余写的以下总结,不对的地方希望大家指点,谢谢,转载,请说明地址
更多详情
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐