A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© HM代景康 高级黑马   /  2013-7-25 13:27  /  944 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文


文件上传的整个流程:
第一阶段: 构造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);
            }
        }

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马