zoukankan      html  css  js  c++  java
  • 文件上传的整个流程

    文件上传的整个流程:

    第一阶段: 构造struts2中针对请求字节流而构造的封闭类MultiPartRequestWrapper

    1.FilterDispatcher

    在doFilter方法中调用了prepareDispatcherAndWrapRequest方法,为了包装出Struts2自己的request对 象,在prepareDispatcherAndWrapRequest方法中调用Dispatcher类的wrapRequest方法,在这个方法里, 会根据请求内容的类型(提交的是文本的,还是multipart/form-data格式),决定是使用tomcat的 HttpServletRequestWrapper类分离出请求中的数据,还是使用Struts2的MultiPartRequestWrapper来 分离请求中的数据。

    注:向服务器请求时,数据是以流的形式向服务器提交,内容是一些有规则东东,我们平时在jsp中用request内置对象取parameter时,实际上是由tomcat的HttpServletRequestWrapper类分解好了的,无需我们再分解这些东西了。


    当然,在这里,我们研究的是上传文件的情况,所以,由于form中设定的提交内容是媒体格式的,所以,Dispatcher类的wrapRequest方法会将请求交由MultiPartRequestWrapper类来处理。


    MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的 HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体 请求的包装类。

    Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。

    在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。

    注:MultiPartRequestWrapper最终的一些操作比如getParameter都是通过调用JakartaMultiPartRequest相应的方法完成的.所以,MultiPartRequestWrapper,JakartaMultiPartRequest都看成是request. 只是有个委托的关系.

    在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执行完了后,这些临时文件当然就没用了)。

    详细代码跟踪:

    Dispatcher:

        public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
            // don't wrap more than once
            if (request instanceof StrutsRequestWrapper) {
                return request;
            }

            String content_type = request.getContentType();
        //根据表单的提交类型转换成MultiPartRequestWrapper.
            if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
                MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
                request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
            } else {
                request = new StrutsRequestWrapper(request);
            }

            return request;
        }

    ->
     //建立 MultiPartRequestWrapper 时解析(parse) request:
     public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) {
            super(request);
            //JakartaMultiPartRequest
            multi = multiPartRequest;
            try {
                //parse的过程中,会判断是否超过struts.multipart.maxSize,感觉stack里面的parameter={}是在parse有错误的时候清空的.
                multi.parse(request, saveDir);
                //如果上传的文件超过struts.multipart.maxSize,会在这里加一个错误.
            for (Object o : multi.getErrors()) {
                    //错误信息:the request was rejected because its size (12555091) exceeds the configured maximum (5242880)
                    String error = (String) o;
                    addError(error);
                }
            } catch (IOException e) {
                addError("Cannot parse request: "+e.toString());
            }
        }

    附:
    //JakartaMultiPartRequest实现自MultiPartRequest接口. 如果想替换JakartaMultiPartRequest,我们要做的也是实现该接口.
    在JakartaMultiPartRequest的parse方法里有下面的代码片段:

    public void parse(HttpServletRequest servletRequest, String saveDir)
                throws IOException {
            DiskFileItemFactory fac = new DiskFileItemFactory();
            // Make sure that the data is written to file
            fac.setSizeThreshold(0);
            if (saveDir != null) {
                fac.setRepository(new File(saveDir));
            }

            // Parse the request
            try {
            //这里是parse的核心代码.将parse的又委托给common-fileupload里的ServletFileUpload,ServletFileUpload extends        

             //FileUpload,FileUpload extends FileUploadBase, 最终是调用了FileUploadBase的parseRequest

                ServletFileUpload upload = new ServletFileUpload(fac);
                upload.setSizeMax(maxSize);
                List items = upload.parseRequest(createRequestContext(servletRequest));
            //----

                for (Object item1 : items) {
                    FileItem item = (FileItem) item1;
                    if (log.isDebugEnabled()) log.debug("Found item " + item.getFieldName());
                    if (item.isFormField()) {
                        log.debug("Item is a normal form field");
                        List<String> values;
                        if (params.get(item.getFieldName()) != null) {
                            values = params.get(item.getFieldName());
                        } else {
                            values = new ArrayList<String>();
                        }

                        // note: see http://jira.opensymphony.com/browse/WW-633
                        // basically, in some cases the charset may be null, so
                        // we're just going to try to "other" method (no idea if this
                        // will work)
                        String charset = servletRequest.getCharacterEncoding();
                        if (charset != null) {
                            values.add(item.getString(charset));
                        } else {
                            values.add(item.getString());
                        }
                        params.put(item.getFieldName(), values);
                    } else {
                        log.debug("Item is a file upload");

                        // Skip file uploads that don't have a file name - meaning that no file was selected.
                        if (item.getName() == null || item.getName().trim().length() < 1) {
                            log.debug("No file has been uploaded for the field: " + item.getFieldName());
                            continue;
                        }

                        List<FileItem> values;
                        if (files.get(item.getFieldName()) != null) {
                            values = files.get(item.getFieldName());
                        } else {
                            values = new ArrayList<FileItem>();
                        }

                        values.add(item);
                        files.put(item.getFieldName(), values);
                    }
                }
            } catch (FileUploadException e) {
                log.error(e);
                errors.add(e.getMessage());
            }
        }


    ->
    下面是common-fileupload 1.2版本中的FileUploadBase里的方法:

    public List /* FileItem */ parseRequest(RequestContext ctx)
                throws FileUploadException {
            try {
                //与之前版本相比, 将判断文件大小等重构到getItemIterator.
                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);
                }
            }
        ......




    -------------------------------------------------------------------------------------------

    struts2里面struts.properties可以显示的配置自定义的MultiPartRequest实现类.而不是使用struts2默认的
    org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest,这种方式common-fileupload提示信息不能国际化.
    如果修改代码,只记日志,不抛出运行时异常来避免页面提交参数丢失.但是引出的问题是如果不抛出运行时异常,是不能阻止文件的上传的. 所以,有人认为还是要抛出运行时异常,同时将错误返回到另一个页面.利用浏览器的回退来复原最初表单的提交数据.

    下面是struts2提供的重置struts.multipart.parser的方式:
     <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"    name="jakarta_yourself"  
     class="com.xxxxx.util.JakartaMultiPartRequest"  
     cope="default" optional="true" /> 

    struts.multipart.parser=jakarta_yourself



    另一种解决方案是抛弃common-fileupload,其它可选的开源上传方式是smart, cos.
    推荐使用cos进行文件的上传.具体可以参考http://www.iteye.com/topic/316626

  • 相关阅读:
    Vue(七)-- 插件
    Vue(六)-- 过滤器、常用内置指令、自定义指令
    Vue(五)-- 生命周期
    Vue(四)-- 事件处理(绑定监听、按键修饰符、取消冒泡、阻止默认事件),v-model的使用
    Two Sum 两数之和
    使用原生JavaScript实现sleep函数
    感恩
    关于AJAX的总结和思考-2
    关于AJAX的一点总结与思考-1
    DNS解析和前端优化点之一
  • 原文地址:https://www.cnblogs.com/highriver/p/2068413.html
Copyright © 2011-2022 走看看