文件上传的整个流程:
第一阶段: 构造struts2中针对请求字节流而构造的封闭类MultiPartRequestWrapper
在doFilter方法中调用了prepareDispatcherAndWrapRequest方法,为了包装出Struts2自己的request对象,在prepareDispatcherAndWrapRequest方法中调用Dispatcher类的wrapRequest方法,在这个方法里,会根据请求内容的类型(提交的是文本的,还是multipart/form-data格式),决定是使用tomcat的HttpServletRequestWrapper类分离出请求中的数据,还是使用Struts2的MultiPartRequestWrapper来分离请求中的数据。
当然,在这里,我们研究的是上传文件的情况,所以,由于form中设定的提交内容是媒体格式的,所以,Dispatcher类的wrapRequest方法会将请求交由MultiPartRequestWrapper类来处理。
MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体请求的包装类。
Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。
在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。
在Struts2的JakartaMultiPartRequest类的parse方法中才会真正来调用commons-fileupload组件的ServletFileUpload类对请求进行解析,至此,Struts2已经实现了将请求转交commons-fileupload组件对请求解析的全过程。
剩下的就是等commons-fileupload组件对请求解析完毕后,拿到分解后的数据,根据field名,依次将分解后的field名和值放到params(HashMap类型)里,同时JakartaMultiPartRequest类重置了HttpServletRequest的好多方法,比如熟知的getParameter、getParameterNames、getParameterValues,实际上都是从解析后得到的那个params对象里拿数据,在这个过程,commons-fileupload组件也乖乖的把上传的文件分析好了,JakartaMultiPartRequest也毫不客气的把分解后的文件一个一个的放到了files(HashMap类型)中,实际上此时,commons-fileupload组件已经所有要上传的文件上传完了。
至此,Struts2实现了对HttpServletRequest类的包装,当回到MultiPartRequestWrapper类后,再取一下上述解析过程中发生的错误,然后把错误加到了自己的errors列表中了。
同样我们会发现在MultiPartRequestWrapper类中,也把HttpServletRequest类的好多方法重载了,毕竟是个包装类嘛,实际上对于上传文件的请求,在Struts2后期的处理中用到的request都是MultiPartRequestWrapper类对象,比如我们调用getParameter时,直接调用的是MultiPartRequestWrapper的getParameter方法,间接调的是JakartaMultiPartRequest类对象的getParameter方法。
第二阶段: 执行inteceptor及action.
doFilter方法中,会进一步调用actionMapper的getMapping方法对url进行解析,找出命名空间和action名等,以备后面根据配置文件调用相应的拦截器和action使用。
关于doFilter方法中下一步对Dispatcher类的serviceAction方法的调用,不再描述,总之在action被调用之前,会首先走到fileUpload拦截器(对应的是FileUploadInterceptor类),在这个拦截器中,会先看一下request是不是 MultiPartRequestWrapper,如果不是,就说明不是上传文件用的request,fildUpload拦截器会直接将控制权交给下一个拦截器;如果是,就会把request对象强转为MultiPartRequestWrapper对象,然后调用hasErrors方法,看看有没有上传时候产生的错误,有的话,就直接加到了Action的错误(Action级别的)中了。
另外,在fileUpload拦截器中会将MultiPartRequestWrapper对象中放置的文件全取出来,把文件、文件名、文件类型取出来,放到request的parameters中,这样到了params拦截器时,就可以轻松的将这些内容注入到Action中了,这也就是为什么fileUpload拦截器需要放在params拦截器前面的理由。在文件都放到request的parameters对象里之后,fileUpload拦截器会继续调用其他拦截器直到Action等执行完毕,他还要做一个扫尾的工作:把临时文件夹中的文件删除(这些文件是由commons-fileupload组件上传的,供你在自己的Action中将文件copy到指定的目录下,当action执行完了后,这些临时文件当然就没用了)。
代码跟踪:
下面是common-fileupload 1.2版本中的FileUploadBase里的方法:
public List /* FileItem */ parseRequest(RequestContext ctx)
throws FileUploadException {
try {
FileItemIterator iter = getItemIterator(ctx);
List items = new ArrayList();
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException(
"No FileItemFactory has been set.");
}while (iter.hasNext()) {
FileItemStream item = iter.next();
FileItem fileItem = fac.createItem(item.getFieldName(),
item.getContentType(), item.isFormField(),
item.getName());
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(),
true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(
"Processing of " + MULTIPART_FORM_DATA
+ " request failed. " + e.getMessage(), e);
} items.add(fileItem);
}return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
}
}
getItemIterator(ctx)方法代码:
public FileItemIterator getItemIterator(RequestContext ctx)
throws FileUploadException, IOException {
return new FileItemIteratorImpl(ctx);
}FileItemIteratorImpl(RequestContext ctx)
throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}String contentType = ctx.getContentType();
if ((null == contentType)
|| (!contentType.toLowerCase().startsWith(MULTIPART))) {
throw new InvalidContentTypeException(
"the request doesn't contain a "
+ MULTIPART_FORM_DATA
+ " or "
+ MULTIPART_MIXED
+ " stream, content type header is "
+ contentType);
}InputStream input = ctx.getInputStream();
if (sizeMax >= 0) {
int requestSize = ctx.getContentLength();
if (requestSize == -1) {
input = new LimitedInputStream(input, sizeMax) {
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex =
new SizeLimitExceededException(
"the request was rejected because"
+ " its size (" + pCount
+ ") exceeds the configured maximum"
+ " (" + pSizeMax + ")",
pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
if (sizeMax >= 0 && requestSize > sizeMax) {
throw new SizeLimitExceededException(
"the request was rejected because its size ("
+ requestSize
+ ") exceeds the configured maximum ("
+ sizeMax + ")",
requestSize, sizeMax);
}
}
}String charEncoding = headerEncoding;
if (charEncoding == null) {
charEncoding = ctx.getCharacterEncoding();
}
boundary = getBoundary(contentType);
if (boundary == null) {
throw new FileUploadException(
"the request was rejected because "
+ "no multipart boundary was found");
}notifier = new MultipartStream.ProgressNotifier(listener,
ctx.getContentLength());
multi = new MultipartStream(input, boundary, notifier);
multi.setHeaderEncoding(charEncoding);
skipPreamble = true;
findNextItem();
}
在wrapRequest方法执行完之后,才会进入拦截器FileUploadInteceptor的intercept方法
在该方法中会将MultiPartRequestWrapper的error转换为action的error.
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);
}
} |
|