struts2 18拦截器详解(十一)
该拦截器处于defaultStack第十的位置,看其名称就知道是用于处理文件上传的,对于文件上传有一点大家应该要了解:struts2处理文件上传内部使用的是commons-fileupload组件,当我们在form表单中把enctype属性设置为"multipart/form-data"时表示这是一个文件上传请求,当struts2接收到这样一个请求后把请求包装在一个MultiPartRequestWrapper对象中,而MultiPartRequestWrapper又包装了JakartaMultiPartRequest,JakartaMultiPartRequest对象才是真正处理文件上传的request对象。
下面说一下该拦截器中三个重要的属性:
1.maximumSize -->允许上传文件上最大大小,单位为byte(字节)
2.allowedTypesSet -->允许上传文件类型集合
3.allowedExtensionsSet -->允许上传文件扩展名集合
必须要这三个条件都符合,文件上传才能成功。
下面是该拦截器的intercept方法:
[java]
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();//获取ActionContext对象
//获取request对象
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()}, ac.getLocale()));
}
//如果不是则直接调用下一个拦截器,因为不需要处理文件上传
return invocation.invoke();
}
ValidationAware validation = null;
Object action = invocation.getAction();//获取当前Action对象
//判断有无实现ValidationAware接口,当Action继承自ActionSupport类时是实现了该接口的
if (action instanceof ValidationAware) {
validation = (ValidationAware) action;
}
//前面说了文件上传时request类型就是MultiPartRequestWrapper
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
//判断文件上传是否有错误,也就是是否符合上面的三个上传条件
if (multiWrapper.hasErrors()) {
for (String error : multiWrapper.getErrors()) {
if (validation != null) {//有错误则添加ActionError,注意validation就是当前Action对象
validation.addActionError(error);
}
LOG.warn(error);
}
}
//获取上传文件参数名(input的name属性值)枚举,因为struts2支持一欠上传多个文件
Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {//迭代
// 当前文件参数名
String inputName = (String) fileParameterNames.nextElement();
// 获取上传文件类型,因为多个文件上传input的name属性可以相同,所以这里是数组
String[] contentType = multiWrapper.getContentTypes(inputName);
if (isNonEmpty(contentType)) {
// 获取上传文件的真实名称(在客户端文件系统中的名称),至于返回值为什么是数组与上同理
String[] fileName = multiWrapper.getFileNames(inputName);
if (isNonEmpty(fileName)) {
// 获取上传的文件
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();
//将上传的文件,文件类型,文件名称存储到ActionContext的parameters Map中
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 {
LOG.warn(getTextMessage(action, "struts.messages.invalid.file", new Object[]{inputName}, ac.getLocale()));
}
} else {
LOG.warn(getTextMessage(action, "struts.messages.invalid.content.type", new Object[]{inputName}, ac.getLocale()));
}
}
// 调用下一个拦截器,当然也调用了Action
String result = invocation.invoke();
// 当Action执行完毕,拦截器执行依次返回又执行到该拦截器,清理文件上传时的临时文件
fileParameterNames = multiWrapper.getFileParameterNames();
while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
String inputValue = (String) fileParameterNames.nextElement();
File[] files = multiWrapper.getFiles(inputValue);
for (File currentFile : files) {
if (LOG.isInfoEnabled()) {
LOG.info(getTextMessage(action, "struts.messages.removing.file", new Object[]{inputValue, currentFile}, ac.getLocale()));
}
if ((currentFile != null) && currentFile.isFile()) {
if (currentFile.delete() == false) {//这里调用了currentFile.delete()将临时文件删除,因为此时在Action对上传的文件处理已经完成
LOG.warn("Resource Leaking: Could not remove uploaded file '"+currentFile.getCanonicalPath()+"'.");
}
}
}
}
//返回上一个拦截器
return result;
}
下面再去看一下该拦截器是如何判断上传的文件是否合法的,即acceptFile方法:
[java]
protected boolean acceptFile(Object action, File file, String filename, String contentType, String inputName, ValidationAware validation, Locale locale) {
boolean fileIsAcceptable = false;
// If it's null the upload failed
if (file == null) {
String errMsg = getTextMessage(action, "struts.messages.error.uploading", new Object[]{inputName}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.warn(errMsg);
} else if (maximumSize != null && maximumSize < file.length()) {
String errMsg = getTextMessage(action, "struts.messages.error.file.too.large", new Object[]{inputName, filename, file.getName(), "" + file.length()}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.warn(errMsg);
} else if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) {
String errMsg = getTextMessage(action, "struts.messages.error.content.type.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.warn(errMsg);
} else if ((! allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, filename))) {
String errMsg = getTextMessage(action, "struts.messages.error.file.extension.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale);
if (validation != null) {
validation.addFieldError(inputName, errMsg);
}
LOG.warn(errMsg);
} else {
fileIsAcceptable = true;
}
return fileIsAcceptable;
}
该方法逻辑很简单,就是依次判断上传的文件是否为null,是否符合设定的文件大小,是否符合设定的文件类型,是否符合设定的文件扩展名。如果没有设定文件类型与文件扩展名则表示文件类型与文件扩展名没有限制,而在defaultStack中,该拦截器的这两个都是没有进行设定的,但是上传文件的最大大小是设置了的,肯定有人会说maximumSize不是也没有设置吗?没错,在该拦截器maximumSize的确没有设置,默认值就是为null,刚刚也说了,真正处理文件上传的request对象是JakartaMultiPartRequest,在JakartaMultiPartRequest中有一个相应的maxSize属性在实例化JakartaMultiPartRequest对象的时候通过struts2的容器把该值注入进来了,该值的配置在struts2-core.jar中的org.apache.struts2包中的default.properties文件中(struts.multipart.maxSize=2097152),默认值为2097152,即2M大小。
我们在进行文件上传的时候是在Action中定义相应的属性来接收上传的文件,文件的类型与文件名,但在大家发现没有在该拦截器并没有调用当前Action设置当前上传文件的方法,即没有调用相应的setXxx()、setXxxContentType()、setXxxFileName()方法,xxx为input的name属性值,只是将上传的文件,文件类型,文件名称存储到ActionContext的parameters Map中,这个问题在ParametersInterceptor拦截器进行讲解。
这个拦截器与前面几个拦截器有一点不一样就是在执行了一定的逻辑后调用了下一个拦截器、Action,在执行完Action拦截器依次返回再返回到该拦截器继续执行清理上传文件时产生的临时文件,这是由该拦截器要实现的功能所决定的。