struts上传下载

来源:互联网 发布:ecs网络拓扑图图标 编辑:程序博客网 时间:2024/06/05 01:10

BlogJava上已经有一位作者阐述了文件上传的问题,地址是Struts 2中实现文件上传,因此我就不再讨论那个话题了。我今天简单介绍一下Struts 2的文件下载问题。

我们的项目名为 struts2hello,所使用的开发环境是MyEclipse 6,当然其实用哪个IDE都是一样的,只要把类库放进去就行了,文件下载不需要再加入任何额外的包。读者可以参考文档:http://beansoft.java-cn.org/myeclipse_doc_cn/struts2_demo.pdf,来了解怎么下载和配置基本的Struts 2开发环境。

为了便于大家对比,我把完整的struts.xml的配置信息列出来:

Xml代码  

<?xml version="1.0" encoding="UTF-8" ?> 

<!DOCTYPE struts PUBLIC 

"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" 

"http://struts.apache.org/dtds/struts-2.0.dtd"> 

<struts> 

<package name="default" extends="struts-default" > 

<!-- 在这里添加Action定义 --> 

10 <!-- 简单文件下载 --> 

11 <action name="download" class="example.FileDownloadAction"> 

12 <result name="success" type="stream"> 

13 <param name="contentType">text/plain</param> 

14 <param name="inputName">inputStream</param> 

15 <param name="contentDisposition">attachment;filename="struts2中文.txt"</param> 

16 <param name="bufferSize">4096</param> 

17 </result> 

18 </action> 

19 

20 <!-- 文件下载,支持中文附件名 --> 

21 <action name="download2" class="example.FileDownloadAction2"> 

22 <!-- 初始文件名 --> 

23 <param name="fileName">Struts中文附件.txt</param> 

24 <result name="success" type="stream"> 

25 <param name="contentType">text/plain</param> 

26 <param name="inputName">inputStream</param> 

27 <!-- 使用经过转码的文件名作为下载文件名,downloadFileName属性 

28 对应action类中的方法 getDownloadFileName() --> 

29 <param name="contentDisposition">attachment;filename="${downloadFileName}"</param> 

30 <param name="bufferSize">4096</param> 

31 </result> 

32 </action> 

33 

34 <!-- 下载现有文件 --> 

35 <action name="download3" class="example.FileDownloadAction3"> 

36 <param name="inputPath">/download/系统说明.doc</param> 

37 <!-- 初始文件名 --> 

38 <param name="fileName">系统说明.doc</param> 

39 <result name="success" type="stream"> 

40 <param name="contentType">application/octet-stream;charset=ISO8859-1</param> 

41 <param name="inputName">inputStream</param> 

42 <!-- 使用经过转码的文件名作为下载文件名,downloadFileName属性 

43 对应action类中的方法 getDownloadFileName() --> 

44 <param name="contentDisposition">attachment;filename="${downloadFileName}"</param> 

45 <param name="bufferSize">4096</param> 

46 </result> 

47 </action> 

48 

49 </package> 

50 

51 </struts> 

Struts 2中对文件下载做了直接的支持,相比起自己辛辛苦苦的设置种种HTTP头来说,现在实现文件下载无疑要简便的多。说起文件下载,最直接的方式恐怕是直接写一个超链接,让地址等于被下载的文件,例如:<a href=file1.zip>下载file1.zip</a>,之后用户在浏览器里面点击这个链接,就可以进行下载了。但是它有一些缺陷,例如如果地址是一个图片,那么浏览器会直接打开它,而不是显示保存文件的对话框。再比如如果文件名是中文的,它会显示一堆URL编码过的文件名例如%3457...。而假设你企图这样下载文件:http://localhost:8080/struts2hello/download/系统说明.docTomcat会告诉你一个文件找不到的404错误:HTTP Status 404 - /struts2hello/download/ϵͳ˵Ã÷.doc。虽然目前还没发现直接配置Struts 2来正确的下载中文名字的附件,不过好在作者对JSP中的文件下载比较了解,因此我们另有办法解决这个问题。另外一个最大的用途,就是动态的生成并下载文件了,例如动态的下载生成的EXCELPDF,验证码图片等等。本节内容就依次讨论简单的下载文件代码,下载中文附件,最后介绍如何下载已经存在的文件。

先说文件下载,编写一个普通的Action就可以了,只需要提供一个返回InputStream流的方法,该输入流代表了被下载文件的入口,这个方法用来给被下载的数据提供输入流,意思是从这个流读出来,再写到浏览器那边供下载。这个方法需要由开发人员自己来编写,只需要返回值为InputStream即可。在我们的例子中方法的签名是:public InputStream getInputStream() throws Exception,当然它也可以是别的名字,例如getDownloadFile()。好了,现在我们所写的这个进行文件下载的Actionexample.FileDownloadAction的源代码清单如下:

Java代码  

52 package example; 

53 

54 import java.io.ByteArrayInputStream; 

55 

56 import java.io.InputStream; 

57 

58 import com.opensymphony.xwork2.Action; 

59 

60 public class FileDownloadAction implements Action { 

61 

62 public InputStream getInputStream() throws Exception { 

63 

64 return new ByteArrayInputStream("Struts 2 下载示例".getBytes()); 

65 

66 

67 

68 public String execute() throws Exception { 

69 

70 return SUCCESS; 

71 

72 

73 

74 

注意这里唯一特殊的方法就是getInputStream(),在这个方法里面我们使用了一个数组输入流来从字符串转换成的数组作为数据的来源进行读取。也许方法体中使用这样的实现代码:

Java代码  

75 return new java.io.FileInputStream(c:\\test.txt);//从系统磁盘文件读取数据  

这样会更直观一些。

文件下载的第二步,乃是在struts.xml中对action进行配置,其代码清单如下所示:

Xml代码  

76 <!-- 简单文件下载 --> 

77 

78 <action name="download" class="example.FileDownloadAction"> 

79 

80 <result name="success" type="stream"> 

81 

82 <param name="contentType">text/plain</param> 

83 

84 <param name="inputName">inputStream</param> 

85 

86 <param name="contentDisposition">attachment;filename="struts2.txt"</param> 

87 

88 <param name="bufferSize">4096</param> 

89 

90 </result> 

91 

92 </action> 

这个action特殊的地方在于result的类型是一个流(stream),配置stream类型的结果时,因为无需指定实际的显示的物理资源,所以无需指定location属性,只需要指定inputName属性,该属性指向被下载文件的来源,对应着Action类中的某个属性,类型为InputStream。下面则列出了和下载有关的一些参数列表:

参数

说明

contentType

内容类型,和互联网MIME标准中的规定类型一致,例如text/plain代表纯文本,text/xml表示XMLimage/gif代表GIF图片,image/jpeg代表JPG图片

inputName

下载文件的来源流,对应着action类中某个类型为Inputstream的属性名,例如取值为inputStream的属性需要编写getInputStream()方法

contentDisposition

文件下载的处理方式,包括内联(inline)和附件(attachment)两种方式,而附件方式会弹出文件保存对话框,否则浏览器会尝试直接显示文件。取值为:

attachment;filename="struts2.txt",表示文件下载的时候保存的名字应为struts2.txt。如果直接写filename="struts2.txt",那么默认情况是代表inline,浏览器会尝试自动打开它,等价于这样的写法:inline; filename="struts2.txt"

bufferSize

下载缓冲区的大小

在这里面,contentType属性和contentDisposition分别对应着HTTP响应中的头Content-TypeContent-disposition头。好,我们先来看看这个例子,发布运行项目后键入测试地址:http://localhost:8080/struts2hello/download.action,将会看到浏览器弹出一个文件保存对话框,如图12.12所示。

12.12 文件下载对话框(IE 7Firefox 3

如果此时使用某些工具来探测浏览器返回的HTTP头,将会看到下列内容:

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

Content-disposition: attachment;filename="struts2.txt"

Content-Type: text/plain

Transfer-Encoding: chunked

Date: Sun, 02 Mar 2008 02:58:25 GMT

。所以用来下载的action配置中,只有两个是和浏览器有关的:contentTypecontentDisposition。关于contentType的取值,如果是未知的文件类型,或者说出现了浏览器不能打开的文件,例如.bean文件,或者说这个action是用来做动态文件下载的,事先并不知道未来的文件类型是什么,那么我们可以把它的值设置成为:application/octet-stream;charset=ISO8859-1,注意一定要加入charset,否则某些时候会导致下载的文件出错;有人说这时也可以设置成为application/x-download,根据笔者的实践,这个头也能正常工作,然而个别时候会出现浏览器无法识别的问题。而contentDisposition,如果其取值是filename="struts2.txt",或者是inline; filename="struts2.txt",运行后你可以看到浏览器直接显示了文件的内容:

Struts 2下载示例,而不再弹出对话框提示用户保存文件到硬盘上。所以读者如果想确保文件是被下载而不是被打开,务必使用格式attachment;filename="struts2.txt",不要丢了attachment;这个类型信息。

至此,关于文件下载的技术内容,已经告一段落。然而做中文系统,不可避免的要解决中文附件的下载问题。关于这个内容,也无权威的资料可查,我们只能用实践中得到的解决方案来处理。也许有读者以为将filename属性设置为filename=struts2中文.txt就能解决问题了,好,就来试试,把contentDisposition修改成:

<param name="contentDisposition">attachment;filename="struts2中文.txt"</param>

。再次键入地址进行测试,看看显示的结果,如图12.13所示。唉,真是完全不给面子!IE压根就不能显示出来文件名,草草敷衍了download_action了事。Firefox稍好点,还出来了一个对话框,但是很显然,那个显示的struts2--txt绝对不是我们日思夜想的struts2中文.txt。怎么办?解决方法是有,那就是用ISO8859-1编码来显示这个中文字符,可以阅读12.8参考资料一节中的JSP文件下载的相对完整代码(解决中文问题和Weblogic报错)这篇文章,可以这样认为,所有的文件下载代码都是基于同样的纯Servlet的方式来进行的。如果是Java代码,我们可以这样做:

12.13 IEFirefox下的中文文件下载对话框

String downFileName = new String(struts2中文.txt.getBytes(), "ISO8859-1");

然后把生成的结果字符串放到XML文件中就行了,然而它的输出类似于struts2??.txt,是无法直接写道我们的XML配置文件中的。所以,我们想到的的办法,就是在Action类中写一个方法来做转码,使它成为某个属性,所以要以get开头。然后,再用12.3.8 Action注入参数(param)值一节的内容,将文件名以正常的方式设置为action类的某个属性,最后呢,再利用一个小小的param参数取值中的伎俩:${属性名},它可以直接从action类中动态获取某个属性值。好了,现在让我们来看看第二个文件下载类FileDownloadAction2的代码:

Java代码  

93 package example; 

94 

95 import java.io.ByteArrayInputStream; 

96 

97 import java.io.InputStream; 

98 

99 import java.io.UnsupportedEncodingException; 

100 

101 import com.opensymphony.xwork2.Action; 

102 

103 public class FileDownloadAction2 implements Action { 

104 

105 private String fileName;// 初始的通过param指定的文件名属性  

106 

107 public InputStream getInputStream() throws Exception { 

108 

109 return new ByteArrayInputStream("Struts 2 下载示例".getBytes()); 

110 

111 

112 

113 public String execute() throws Exception { 

114 

115 return SUCCESS; 

116 

117 

118 

119 public void setFileName(String fileName) { 

120 

121 this.fileName = fileName; 

122 

123 

124 

125 /** 提供转换编码后的供下载用的文件名 */ 

126 

127 public String getDownloadFileName() { 

128 

129 String downFileName = fileName; 

130 

131 try { 

132 

133 downFileName = new String(downFileName.getBytes(), "ISO8859-1"); 

134 

135 catch (UnsupportedEncodingException e) { 

136 

137 e.printStackTrace(); 

138 

139 

140 

141 return downFileName; 

142 

143 

144 

145 

这个类有两个属性,第一个是fileName,它是需要被指定的下载文件名;第二个则是动态的仅仅由getDownloadFileName()这个方法定义的属性downloadFileName,它的值随着fileName而动态变动,仅仅是把它转换成了ISO8859方式的西欧字符集。

接下来就是如何配置这个action了,这是关键的地方所在,现在配置一个新的action,名为download2,其源代码如下:

Xml代码  

146 <!-- 文件下载,支持中文附件名 --> 

147 

148 <action name="download2" class="example.FileDownloadAction2"> 

149 

150 <!-- 初始文件名 --> 

151 

152 <param name="fileName">Struts中文附件.txt</param> 

153 

154 <result name="success" type="stream"> 

155 

156 <param name="contentType">text/plain</param> 

157 

158 <param name="inputName">inputStream</param> 

159 

160 <!-- 使用经过转码的文件名作为下载文件名,downloadFileName属性 

161 

162 对应action类中的方法 getDownloadFileName() --> 

163 

164 <param name="contentDisposition">attachment;filename="${downloadFileName}"</param> 

165 

166 <param name="bufferSize">4096</param> 

167 

168 </result> 

169 

170 </action> 

其中特殊的代码就是${downloadFileName},它的效果相当于运行的时候将action对象的属性的取值动态的填充在${}中间的部分,我们可以认为它等价于action.getDownloadFileName()

好了,现在让我们重新发布然后运行这个项目,键入地址:

http://localhost:8080/struts2hello/download2.action进行访问,可以看到运行结果完全正确,如图12.14所示。

图 12.14 正确显示了文件下载名的对话框(IEFirefox

在本节的最后部分,我们来讨论一下如何下载已经存在于当前Web应用目录下的已经存在的文件。一般的网站可能会把要下载的文件放在某个固定的目录下,例如WebRoot/download,在这个子目录下,我们放了一个名为系统说明.doc的文件,希望最后我们的action能够正确的下载这个文件。要检验下载是否成功非常简单,文件内容仅仅是粗体的系统说明书这五个字,而word文件坏一个字节的话都是打不开的,所以下载后再用word打开即可检验是否成功。现在我们创建第三个文件下载的Action类,名为example. FileDownloadAction3,其源代码清单如下所示:

Java代码  

171 package example; 

172 

173 import java.io.InputStream; 

174 

175 import java.io.UnsupportedEncodingException; 

176 

177 import org.apache.struts2.ServletActionContext; 

178 

179 import com.opensymphony.xwork2.Action; 

180 

181 public class FileDownloadAction3 implements Action { 

182 

183 private String fileName;// 初始的通过param指定的文件名属性  

184 

185 private String inputPath;// 指定要被下载的文件路径  

186 

187 

188 public InputStream getInputStream() throws Exception { 

189 

190 // 通过 ServletContext,也就是application 来读取数据  

191 

192 return ServletActionContext.getServletContext().getResourceAsStream(inputPath); 

193 

194 

195 

196 public String execute() throws Exception { 

197 

198 return SUCCESS; 

199 

200 

201 

202 public void setInputPath(String value) { 

203 

204 inputPath = value; 

205 

206 

207 

208 public void setFileName(String fileName) { 

209 

210 this.fileName = fileName; 

211 

212 

213 

214 /** 提供转换编码后的供下载用的文件名 */ 

215 

216 public String getDownloadFileName() { 

217 

218 String downFileName = fileName; 

219 

220 try { 

221 

222 downFileName = new String(downFileName.getBytes(), "ISO8859-1"); 

223 

224 catch (UnsupportedEncodingException e) { 

225 

226 e.printStackTrace(); 

227 

228 

229 

230 return downFileName; 

231 

232 

233 

234 

代码中被改动的部分已经用粗斜体的方式显示出来了。首先是新加入了一个名为inputPath的属性,用来制定被下载文件的路径。接着就是ServletActionContext.getServletContext()这段代码,它的意义我们将在12.6节详细讨论,在这里读者只需要知道它获取了当前Servlet容器的ServletContext,也就是大家常说的jsp中的application对象,然后用它来打开文件的输入流。

接着要做的就是配置action,它和刚刚配置过的download2的内容差不多,只是多了一个被下载的资源的路径属性。现在我们在struts.xml中加入这个新的action定义:

Xml代码  

235 <!-- 下载现有文件 --> 

236 

237 <action name="download3" class="example.FileDownloadAction3"> 

238 

239 <param name="inputPath">/download/系统说明.doc</param> 

240 

241 <!-- 初始文件名 --> 

242 

243 <param name="fileName">系统说明.doc</param> 

244 

245 <result name="success" type="stream"> 

246 

247 <param name="contentType">application/octet-stream;charset=ISO8859-1</param> 

248 

249 <param name="inputName">inputStream</param> 

250 

251 <!-- 使用经过转码的文件名作为下载文件名,downloadFileName属性 

252 

253 对应action类中的方法 getDownloadFileName() --> 

254 

255 <param name="contentDisposition">attachment;filename="${downloadFileName}"</param> 

256 

257 <param name="bufferSize">4096</param> 

258 

259 </result> 

260 

261 </action> 

查看粗斜体的部分,首先就是自定了被下载文件的路径,inputPath,接着就是修改了contentType为二进制方式。最后重新发布项目并运行,键入地址进行访问:http://localhost:8080/struts2hello/download3.action。很好,可以看到文件下载对话框,保存系统说明.doc后再用word打开它,内容正确。

注意:而这种文件下载方式却是存在安全隐患的,因为访问者如果精通Struts 2的话,它可能使用这样的带有表单参数的地址来访问:http://localhost:8080/struts2hello/download3.action?inputPath=/WEB-INF/web.xml,这样的结果就是下载后的文件内容是您系统里面的web.xml的文件的源代码,甚至还可以用这种方式来下载任何其它JSP文件的源码。这对系统安全是个很大的威胁。作为一种变通的方法,读者最好是从数据库中进行路径配置,然后把Action类中的设置inputPath的方法统统去掉,简言之就是删除这个方法定义:

public void setInputPath(String value) {

inputPath = value;

}

。而实际情况则应该成为 download3.action?fileid=1类似于这样的形式来进行。或者呢,读者可以在execute()方法中进行路径检查,如果发现有访问不属于download下面文件的代码,就一律拒绝,不给他们返回文件内容。例如,我们可以把刚才类中的execute()方法加以改进,成为这样:

Java代码  

262 public String execute() throws Exception { 

263 

264 // 文件下载目录路径  

265 

266 String downloadDir = ServletActionContext.getServletContext().getRealPath("/download"); 

267 

268 // 文件下载路径  

269 

270 String downloadFile = ServletActionContext.getServletContext().getRealPath(inputPath); 

271 

272 java.io.File file = new java.io.File(downloadFile); 

273 

274 downloadFile = file.getCanonicalPath();// 真实文件路径,去掉里面的..等信息  

275 

276 // 发现企图下载不在 /download 下的文件, 就显示空内容  

277 

278 if(!downloadFile.startsWith(downloadDir)) { 

279 

280 return null

281 

282 

283 

284 return SUCCESS; 

285 

286 

这时候如果访问者再企图下载web.xml的内容,它只能得到一个空白页,现在访问者只能下载位于/download目录下的文件。

Struts 2中实现文件上传

前一阵子有些朋友在电子邮件中问关于Struts 2实现文件上传的问题, 所以今天我们就来讨论一下这个问题。

实现原理

Struts 2是通过Commons FileUpload文件上传。Commons FileUpload通过将HTTP的数据保存到临时文件夹,然后Struts使用fileUpload拦截器将文件绑定到Action的实例中。从而我们就能够以本地文件方式的操作浏览器上传的文件。

具体实现

前段时间Apache发布了Struts 2.0.6 GA,所以本文的实现是以该版本的Struts作为框架的。以下是例子所依赖类包的列表:


清单依赖类包的列表

首先,创建文件上传页面FileUpload.jsp,内容如下:

<%@ page language="java"contentType="text/html; charset=utf-8"pageEncoding="utf-8"%>
<%@ taglib prefix="s"uri="/struts-tags"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <title>Struts 2 File Upload</title>
</head>
<body>
 <s:form action="fileUpload"method="POST"enctype="multipart/form-data">
 <s:file name="myFile"label="Image File"/>
 <s:textfield name="caption"label="Caption"/> 
 <s:submit />
 </s:form>
</body>
</html>

清单2 FileUpload.jspFileUpload.jsp中,先将表单的提交方式设为POST,然后将enctype设为multipart/form-data,这并没有什么特别之处。接下来,<s:file/>标志将文件上传控件绑定到ActionmyFile属性。

其次是FileUploadAction.java代码:

packagetutorial;

importjava.io.BufferedInputStream;
importjava.io.BufferedOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.util.Date;

importorg.apache.struts2.ServletActionContext;

importcom.opensymphony.xwork2.ActionSupport;

publicclassFileUploadAction extendsActionSupport  {
  private static final long serialVersionUID = 572146812454l ;
  private static final int BUFFER_SIZE = 16 * 1024 ;
 
  private File myFile;
  private String contentType;
  private String fileName;
  private String imageFileName;
  private String caption;
 
  public void setMyFileContentType(String contentType)  {
  this .contentType = contentType;
 } 
 
  public void setMyFileFileName(String fileName)  {
  this .fileName = fileName;
 } 
 
  public void setMyFile(File myFile)  {
  this .myFile = myFile;
 } 
 
  public String getImageFileName()  {
  return imageFileName;
 } 
 
  public String getCaption()  {
  return caption;
 } 
 
   public void setCaption(String caption)  {
  this .caption = caption;
 } 
 
  private static void copy(File src, File dst)  {
  try  {
 InputStream in = null ;
 OutputStream out = null ;
  try  
 in = new BufferedInputStream( new FileInputStream(src), BUFFER_SIZE);
 out = new BufferedOutputStream( new FileOutputStream(dst), BUFFER_SIZE);
  byte [] buffer = new byte [BUFFER_SIZE];
  while (in.read(buffer) > 0  {
 out.write(buffer);
 } 
  } finally  {
  if ( null != in)  {
 in.close();
 } 
   if ( null != out)  {
 out.close();
 } 
 } 
  } catch (Exception e)  {
 e.printStackTrace();
 } 
 } 
 
  private static String getExtention(String fileName)  {
  int pos = fileName.lastIndexOf( " . " );
  return fileName.substring(pos);
 } 
 
 @Override
  public String execute()   
 imageFileName = new Date().getTime() + getExtention(fileName);
 File imageFile = new File(ServletActionContext.getServletContext().getRealPath( " /UploadImages " ) + " / " + imageFileName);
 copy(myFile, imageFile);
  return SUCCESS;
 } 
 
} 

清单3 tutorial/FileUploadAction.javaFileUploadAction中我分别写了setMyFileContentTypesetMyFileFileNamesetMyFilesetCaption四个Setter方法,后两者很容易明白,分别对应FileUpload.jsp中的<s:file/><s:textfield/>标志。但是前两者并没有显式地与任何的页面标志绑定,那么它们的值又是从何而来的呢?其实,<s:file/>标志不仅仅是绑定到myFile,还有myFileContentType(上传文件的MIME类型)和myFileFileName(上传文件的文件名,该文件名不包括文件的路径)。因此,<s:file name="xxx" />对应Action类里面的xxxxxxContentTypexxxFileName三个属性。

FileUploadAction作用是将浏览器上传的文件拷贝到WEB应用程序的UploadImages文件夹下,新文件的名称是由系统时间与上传文件的后缀组成,该名称将被赋给imageFileName属性,以便上传成功的跳转页面使用。

下面我们就来看看上传成功的页面:

<%@ page language="java"contentType="text/html; charset=utf-8"pageEncoding="utf-8"%>
<%@ taglib prefix="s"uri="/struts-tags"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <title>Struts 2 File Upload</title>
</head>
<body>
 <div style="padding: 3px; border: solid 1px #cccccc; text-align: center">
 <img src='UploadImages/<s:property value="imageFileName"/>' />
 <br />
 <s:property value="caption"/>
 </div>
</body>
</html>

清单4 ShowUpload.jspShowUpload.jsp获得imageFileName,将其UploadImages组成URL,从而将上传的图像显示出来。

然后是Action的配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
 "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
 "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 <package name="fileUploadDemo"extends="struts-default">
 <action name="fileUpload"class="tutorial.FileUploadAction">
 <interceptor-ref name="fileUploadStack"/>
 <result name="success">/ShowUpload.jsp</result>
 </action>
 </package>
</struts>

清单5 struts.xmlfileUpload Action显式地应用fileUploadStack的拦截器。

最后是web.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9"version="2.4"
 xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

 <display-name>Struts 2 Fileupload</display-name>

 <filter>
 <filter-name>struts-cleanup</filter-name>
 <filter-class>
 org.apache.struts2.dispatcher.ActionContextCleanUp
 </filter-class>
 </filter>
 
 <filter>
 <filter-name>struts2</filter-name>
 <filter-class>
 org.apache.struts2.dispatcher.FilterDispatcher
 </filter-class>
 </filter>
 
 <filter-mapping>
 <filter-name>struts-cleanup</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>

 <filter-mapping>
 <filter-name>struts2</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>

 <welcome-file-list>
 <welcome-file>index.html</welcome-file>
 </welcome-file-list>

</web-app>

清单6 WEB-INF/web.xml发布运行应用程序,在浏览器地址栏中键入:http://localhost:8080/Struts2_Fileupload/FileUpload.jsp,出现图示页面:


清单7 FileUpload页面

选择图片文件,填写Caption并按下Submit按钮提交,出现图示页面:


清单上传成功页面

更多配置

在运行上述例子,如果您留心一点的话,应该会发现服务器控制台有如下输出:

Mar 20,20074:08:43PM org.apache.struts2.dispatcher.Dispatcher getSaveDir
INFO: Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir
Mar 20,20074:08:43PM org.apache.struts2.interceptor.FileUploadInterceptor intercept
INFO: Removing file myFile C:\Program Files\Tomcat 5.5\work\Catalina\localhost\Struts2_Fileupload\upload_251447c2_1116e355841__7ff7_00000006.tmp

清单服务器控制台输出上述信息告诉我们,struts.multipart.saveDir没有配置。struts.multipart.saveDir用于指定存放临时文件的文件夹,该配置写在struts.properties文件中。例如,如果在struts.properties文件加入如下代码:

struts.multipart.saveDir=/tmp

清单10 struts配置这样上传的文件就会临时保存到你根目录下的tmp文件夹中(一般为c:\tmp),如果此文件夹不存在,Struts 2会自动创建一个。

错误处理

上述例子实现的图片上传的功能,所以应该阻止用户上传非图片类型的文件。在Struts 2中如何实现这点呢?其实这也很简单,对上述例子作如下修改即可。

首先修改FileUpload.jsp,在<body><s:form>之间加入<s:fielderror />,用于在页面上输出错误信息。

然后修改struts.xml文件,将Action fileUpload的定义改为如下所示:

<action name="fileUpload"class="tutorial.FileUploadAction">
 <interceptor-ref name="fileUpload">
 <param name="allowedTypes">
 image/bmp,image/png,image/gif,image/jpeg
 </param>
 </interceptor-ref>
 <interceptor-ref name="defaultStack"/> 
 <result name="input">/FileUpload.jsp</result>
 <result name="success">/ShowUpload.jsp</result>
 </action>

清单11 修改后的配置文件显而易见,起作用就是fileUpload拦截器的allowTypes参数。另外,配置还引入defaultStack它会帮我们添加验证等功能,所以在出错之后会跳转到名称为input的结果,也即是FileUpload.jsp

发布运行应用程序,出错时,页面如下图所示:


清单12 出错提示页面

上面的出错提示是Struts 2默认的,大多数情况下,我们都需要自定义和国际化这些信息。通过在全局的国际资源文件中加入struts.messages.error.content.type.not.allowed=The file you uploaded is not a image,可以实现以上提及的需求。对此有疑问的朋友可以参考我之前的文章《Struts 2.0中国际化(i18n)您的应用程序》。

实现之后的出错页面如下图所示:


清单13 自定义出错提示页面

同样的做法,你可以使用参数maximumSize来限制上传文件的大小,它对应的字符资源名为:struts.messages.error.file.too.large

字符资源struts.messages.error.uploading用提示一般的上传出错信息。

多文件上传

与单文件上传相似,Struts 2实现多文件上传也很简单。你可以将多个<s:file />绑定Action的数组或列表。如下例所示。

<s:form action="doMultipleUploadUsingList"method="POST"enctype="multipart/form-data">
 <s:file label="File (1)"name="upload"/>
 <s:file label="File (2)"name="upload"/>
 <s:file label="FIle (3)"name="upload"/>
 <s:submit />
</s:form>

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

 privateFile[] uploads;
 privateString[] uploadFileNames;
 privateString[] uploadContentTypes;

 publicFile[] getUpload()  return this .uploads; } 
  publicvoidsetUpload(File[] upload)  this .uploads = upload; } 
 
  publicString[] getUploadFileName()  return this .uploadFileNames; } 
  publicvoidsetUploadFileName(String[] uploadFileName)  this .uploadFileNames = uploadFileName; } 
 
  publicString[] getUploadContentType()  return this .uploadContentTypes; } 
  publicvoidsetUploadContentType(String[] uploadContentType)  this .uploadContentTypes = uploadContentType; } 

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

 privateList<File>uploads =newArrayList<File>();
 privateList<String>uploadFileNames =newArrayList<String>();
 privateList<String>uploadContentTypes =newArrayList<String>();

 publicList<File>getUpload()  {
  return this .uploads;
 } 
  publicvoidsetUpload(List<File>uploads)  {
  this .uploads = uploads;
 } 
 
  publicList<String>getUploadFileName()  {
  return this .uploadFileNames;
 } 
  publicvoidsetUploadFileName(List<String>uploadFileNames)  {
  this .uploadFileNames = uploadFileNames;
 } 
 
  publicList<String>getUploadContentType()  {
  return this .uploadContentTypes;
 } 
  publicvoidsetUploadContentType(List<String>contentTypes)  {
  this .uploadContentTypes = contentTypes;
 } 

清单16 多文件上传列表绑定Action代码片段总结

Struts 2中实现文件上传的确是轻而易举,您要做的只是使用<s:file />Action的属性绑定。这又一次有力地证明了Struts 2的简单易用。

原创粉丝点击