之前我们可能使用过文件上传组件进行开发系统中文件上传的功能,但在Struts2 中为我们提供了更为简单易用的上传功能,当然虽然易用,但其实际不是对底层文件IO和HTTP的封装。下面我们介绍一下,如何在Struts2 中使用文件上传的功能。
我们知识Strust2中大部分的功能都是通过拦截器实现的,当然,这里的文件上传也不例外。同样也是也采用拦截器来支持的。
下面以例子来看一下如何使用strsut2 实现文件上传:
1. 在myeclipse 下创建一个strust2 web 工程,正常配置struts.xml。
2. 在web目录下面创建一个upload.jsp 页面,做为页面上传的界面,代码如下:
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>register page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
<script type="text/javascript">
function addMore(){
var td=document.getElementById("more");
var br=document.createElement("br");
var input=document.createElement("input");
var button=document.createElement("input");
input.type="file";
input.name="file";
button.type="button";
button.value="remove";
button.onclick=function(){
td.removeChild(br);
td.removeChild(input);
td.removeChild(button);
}
td.appendChild(br);
td.appendChild(input);
td.appendChild(button);
}
</script>
</head>
<body>
<br>
<s:fielderror />
<s:form action="upload" theme="simple" enctype="multipart/form-data" >
<table border="1" width="500">
<tr>
<td colspan="2">文件上传</td>
</tr>
<tr>
<td>username</td>
<td><s:textfield name="username"/></td>
</tr>
<tr>
<td>password</td>
<td><s:password name="password" id="password"/></td>
</tr>
<tr>
<td>file1</td>
<td id="more"><s:file name="file" id="file"/><input type="button" onclick="addMore()" value="添加..."/></td>
</tr>
<tr>
<td></td>
<td><s:submit name="submit"/></td>
</tr>
</table>
</s:form>
</body>
</html>
界面显示如下:
该例子是一个用户可以指定上传文件个数进行上伟的例子,单击添加按钮可以新增上传控制,这个通过javascript 脚本实现的。注意添加的组件的名字都是file,这样上传的文件会传到Action中的一个List中,下面会看到。
1.4创建对应的Action; UploadAction 代码如下:
package com.snt.struts2.action;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class UploadAction extends ActionSupport{
private String username; //对应页面的属性
private String password;
private List<File> file; //上传文件列表
private List<String> fileFileName; //上传文件名
private List<String> fileContentType; //上传文件内容类型 这两个值strust2会自动注入
public List<File> getFile() {
return file;
}
public void setFile(List<File> file) {
this.file = file;
}
public List<String> getFileContentType() {
return fileContentType;
}
public void setFileContentType(List<String> fileContentType) {
this.fileContentType = fileContentType;
}
public List<String> getFileFileName() {
return fileFileName;
}
public void setFileFileName(List<String> fileFileName) {
this.fileFileName = fileFileName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String execute() throws Exception {
for(int i=0;i<file.size();i++){
InputStream is=new FileInputStream(file.get(i));
String root=ServletActionContext.getRequest().getRealPath("/upload");
File destFile=new File(root,this.getFileFileName().get(i));
OutputStream os=new FileOutputStream(destFile);
byte[] buffer=new byte[400];
int length=0;
while((length=is.read(buffer))>0){
os.write(buffer,0,length);
}
is.close();
os.close();
}
return SUCCESS;
}
}
代码中橙色背景显示的代码 List<File> file 对应页面中要上传的文件,映射到Action中就是File 对象,这种映射是由Strust2的拦截器来实现的。
我们查看strust2 中的strust-detault.xml 文件中,会在默认的拦截器链中发一个fileUpload 的拦截,
在strust2 核心包中 org.apache.struts2.interceptor.FileUploadIntercepotr 这个拦截器,下面是这个拦截器的拦截方法:
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext(); Action上下文
//获取用户请求判断是否是文件上传请求
HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);
if (!(request instanceof MultiPartRequestWrapper)) {
if (log.isDebugEnabled()) {
ActionProxy proxy = invocation.getProxy();
log.debug(getTextMessage("struts.messages.bypass.request", new Object[]{proxy.getNamespace(), proxy.getActionName()}, ActionContext.getContext().getLocale()));
}
return invocation.invoke();
}
final Object action = invocation.getAction();
ValidationAware validation = null;
//验证
if (action instanceof ValidationAware) {
validation = (ValidationAware) action;
}
//获取文件上传请求的一个包装类对象
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
if (multiWrapper.hasErrors()) {
for (Iterator errorIter = multiWrapper.getErrors().iterator(); errorIter.hasNext();) {
String error = (String) errorIter.next();
if (validation != null) {
validation.addActionError(error);
}
log.error(error);
}
}
//大家看这句话,是从Action上下文件环境中取出所以的参数
Map parameters = ac.getParameters();
// Bind allowed Files
//下面是关键的代码,先取得上传文件的参数
//然后再设置到Action中的File 对象
Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
// get the value of this input tag
String inputName = (String) fileParameterNames.nextElement();
// get the content type
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) {
for (int index = 0; index < files.length; index++) {
if (acceptFile(files[index], contentType[index], inputName, validation, ac.getLocale())) {
parameters.put(inputName, files);
//下面这两行代码下好解释我们在Action中声明的两个属性,
//fileFileName fileContentType ,拦截器会自动给它们注入值勤
parameters.put(inputName + "ContentType", contentType);
parameters.put(inputName + "FileName", fileName);
}
}
}
} else {
log.error(getTextMessage("struts.messages.invalid.file", new Object[]{inputName}, ActionContext.getContext().getLocale()));
}
} else {
log.error(getTextMessage("struts.messages.invalid.content.type", new Object[]{inputName}, ActionContext.getContext().getLocale()));
}
}
// invoke action
String result = invocation.invoke();
// cleanup
fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
String inputValue = (String) fileParameterNames.nextElement();
File[] file = multiWrapper.getFiles(inputValue);
for (int index = 0; index < file.length; index++) {
File currentFile = file[index];
log.info(getTextMessage("struts.messages.removing.file", new Object[]{inputValue, currentFile}, ActionContext.getContext().getLocale()));
if ((currentFile != null) && currentFile.isFile()) {
currentFile.delete();
}
}
}
return result;
}
通过这个拦截器,Strsut2 2自动将上传文件对应到Action中的File对象,Action中再通过IO流将其写入到磁盘文件。这就是上传的原理。
在execute方法 中的代码是简单的文件流操作。
1.5上面UploadAction写在,在strust.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>
<constant name="struts.custom.i18n.resources" value="message" />
<constant name="struts.multipart.saveDir" value="D:/"></constant>
<package name="struts2" extends="struts-default">
<!-- 配置拦截器 -->
<interceptors>
<!-- 自定义拦截器栈 -->
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<action name="upload" class="com.snt.struts2.action.UploadAction">
<!-- 注意拦截顺序,需要覆盖的先配置 -->
<interceptor-ref name="fileUpload">
<result name="success">/uploadResult.jsp</result>
<result name="input">/upload.jsp</result>
</action>
</package>
</struts>
好配置好了!
1.6 部署应用程序到tomcat 下面,运行测试:
可以正常上传文件。
1.7 上面的我们实现的文件上传,但是在实际的开发应用中,上传文件的文件类型和大小都是有限制的。
在Strust2 中通过配置方式就可以实现。
我们先看一下拦截器FileUploadInterceptor 拦截器中的属性如下:
public class FileUploadInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = -4764627478894962478L;
protected static final Log log = LogFactory.getLog(FileUploadInterceptor.class);
private static final String DEFAULT_DELIMITER = ",";
private static final String DEFAULT_MESSAGE = "no.message.found";
protected Long maximumSize;
protected String allowedTypes;
protected Set allowedTypesSet = Collections.EMPTY_SET;
当我们看到上面三个属性时,应该可以想到strust2 是如何限制上传文件类型的大小的。
所以我们只需要在配置Action时,修改一个拦截器的配置就可以了,如下配置:
<action name="upload" class="com.snt.struts2.action.UploadAction">
<!-- 注意拦截顺序,需要覆盖的先配置 -->
<interceptor-ref name="fileUpload">
<param name="maximumSize">409600</param>
<param name="allowedTypes">application/vnd.ms-excel</param>
</interceptor-ref>
<interceptor-ref name="myStack" />
<result name="success">/uploadResult.jsp</result>
<result name="input">/upload.jsp</result>
</action>
在配置文件上传拦截器时加两个参数配置即可。
再次部署测试应用程序,发现可以如果文件类型不对或大小过限,则页面又跳回上传页面,说明我们配置的起作用,这是因为上传程序出现异常,程序跳转到页面.在控制我们可以看到“严重”的报错信息.
如果我们在页面上加一句: <s:filederror /> ,运行就可以显示出错的原因,但是这样的报错信息显示太不友好。其实报错的信息就是一条Field 级别的错误信息。
如何屏蔽呢?
之前在struts2 输入校验的时候,讲在message.proeprties 文件中配置可以屏蔽校验的出错信息。
所以我们可以在这里配置信息:
但是现在又不知道,报告的信息的key 值是什么?
我们打开sturst2 核心包中strust-message.proeprties 文件,内容如下:
struts.messages.invalid.token=The form has already been processed or no token was supplied, please try again.
struts.internal.invalid.token=Form token {0} does not match the session token {1}.
struts.messages.bypass.request=Bypassing {0}/ {1}
struts.messages.current.file=File {0} {1} {2} {3}
struts.messages.invalid.file=Could not find a Filename for {0}. Verify that a valid file was submitted.
struts.messages.invalid.content.type=Could not find a Content-Type for {0}. Verify that a valid file was submitted.
struts.messages.removing.file=Removing file {0} {1}
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File too large: {0} "{1}" {2}
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" {2}
我们可以找到其中几个就是我们想要信息。
struts.messages.error.file.too.large 代表上传文件过大会显示的信息
struts.messages.error.content.type.not.allowed 代表上传文件类型不允许,会显示的信息,我们要覆盖这样的信息,需要在classes 目录下[src目录下]的
message.proeprteis 文件中配置
truts.messages.error.content.type.not.allowed=/u4e0a/u4f20/u6587/u4ef6/u7c7b/u578b/u4e0d/u5339/u914d,/u8bf7/u91cd/u8bd5
struts.messages.error.file.too.large=/u4e0a/u4f20/u6587/u4ef6/u8fc7/u5927,/u8bf7/u91cd/u8bd5
上面显示的汉字要通过native2ascii 工具进行编码转化。
再次运行测试就可以正常显示我们配置的信息。
到此,我们已经实现了一个完整功能的文件上传例子。
当然上面是个多文件上传的例子,如果是单个文件,只要在Action中将List改为单个对象,execute 代码稍作修改即可。
另外需要注意的是strsuts.xml 中配置的两具常量,这个常量:
国际化信息配置文件指定
<constant name="struts.custom.i18n.resources" value="message" />
上传文件的临时保存目录
<constant name="struts.multipart.saveDir" value="D:/"></constant>