spring基本使用(20)-springMVC4-SpringMVC九大组件之文件上传解析器MultipartResolver

1、在前面一篇文章中我们讲解了DispatcherServlet的工作流程,第一步会检查是否是文件上传请求,其源码提现如下:

       processedRequest = checkMultipart(request);

 

	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
                如果DispatcherServlet的multipartResolver解析器存在,且解析器解析了请求是文件上传请求。那就去解析请求的文件内容。
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
				logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
						"this typically results from an additional MultipartFilter in web.xml");
			}
			else if (hasMultipartException(request) ) {
				logger.debug("Multipart resolution failed for current request before - " +
						"skipping re-resolution for undisturbed error rendering");
			}
			else {
				try {
                                        解析请求的文件内容。
					return this.multipartResolver.resolveMultipart(request);
				}
				catch (MultipartException ex) {
					if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
						logger.debug("Multipart resolution failed for error dispatch", ex);
						// Keep processing error dispatch with regular request handle below
					}
					else {
						throw ex;
					}
				}
			}
		}
		// If not returned before: return original request.
		return request;
	}

        1.1、那么DispatcherServlet的文件解析器multipartResolver到底是个什么东西呢?????  我们找到了在DispatcherServlet中文件解析器的成员属性如下:

	      /** MultipartResolver used by this servlet */
	      private MultipartResolver multipartResolver;

        1.2、MultipartResolver 接口:   

                 接口源码:

      public interface MultipartResolver {

              根据请求判断是否是文件上传解析器
	      boolean isMultipart(HttpServletRequest request);
 
              根据请求解析请求中的文件内容
	      MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws 
         MultipartException;

              清楚请求中的文件内容。
	      void cleanupMultipart(MultipartHttpServletRequest request);

       }

                  接口类图:

                              

                             只有两个实现,我们是使用哪一个呢??? 我们知道在配置SpringMVC应用上下文的时候,默认的DispatcherServlet.properties中不会帮我们配置默认的文件上传解析器MultipartResolver,。即使我们使用注解驱动<mvc:annotation-driven/>也不会帮我们配置文件上传解析器MultipartResolver。 那么 也就是说文件上传解析器MultipartResolver需要我们自己手动配置进去,不然在DispatcherServlet中的multipartResolver属性就是null。

                              那么我们一般是怎么配置文件上传解析器的呢???? 一般情况下我们都是使用CommonsMultipartResolver,这个解析器呢需要依赖Apache Commons FileUpload 的jar包,体现如下,在CommonsMultipartResolver源码中如果我们项目没有依赖Apache Commons FileUpload 的jar包那么会有如下现象:

                                

                            所以我们先要添加Apache Commons FileUpload的依赖:

              <dependency>
                  <groupId>commons-fileupload</groupId>
                  <artifactId>commons-fileupload</artifactId>
                  <version>1.3.1</version>
              </dependency>
              由于commons-fileupload编译器依赖了commons-io,所以也需要添加commons-io依赖
              <dependency>
                  <groupId>commons-io</groupId>
                  <artifactId>commons-io</artifactId>
                  <version>2.4</version>
              </dependency>

                        接下来就是SpringMVC的应用上下文中配置一个beanName = multipartResolver的CommonsMultipartResolver:

                         我们基于xml来配置,springboot项目也是一样的配置方式

<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">

        <!--上传的零食目录,在CommonsMultipartResolver解析完成文件后,会将文件存在此目录下,当清求结束后会调用
        cleanupMultipart(processedRequest)方法删除储存的临时文件。
        不配置默认是servletContext.getAttribute(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE);
             WebUtils.TEMP_DIR_CONTEXT_ATTRIBUT="javax.servlet.context.tempdir"也就是servlet容器的临时目录
             例如tomcat的就在是apache-tomcat-8.5.0\work\Catalina\localhost\ROOT 目录下-->
        <property name="uploadTempDir" ref="fileSystemResource"/>

        <!--解析文件时候的编码-->
        <property name="defaultEncoding" value="utf-8"/>

        <!--设置单个文件的最大字节数 默认是不限制,我们设置为100M-->
        <property name="maxUploadSize" value="104857600"/>

        <!--设置单次上传,如果有多个文件,限制总大小, 默认不限制,我们设置为300M-->
        <property name="maxUploadSizePerFile" value="#{104857600 * 3}"/>

        <!--保留客户端发送的文件名称,如果为true,当我们调用MultipartFile.getOriginalFilename()的时候就能直接返回客户端发送的文件名称,
         如果是false,将会采用截取的方式获取文件名称,所以我们尽量设置为true
         客户端发送的名称是啥??? 其实就是你电脑上的磁盘中文件的名称,比如D:/file/test.java  客户端名称就是test.java-->
        <property name="preserveFilename" value="true"/>

        <!--是否延迟解析文件如果是false会在checkMultipart(request)阶段就会解析文件,如果是true 会在使用
        HandlerAdapter执行处理器的时候给Controller方法设置入参的时候调用getMultipartFiles()的时候才会解析文件,默认是false-->
        <property name="resolveLazily" value="false"/>

        <!--允许将临时文件存在内存中的最大字节数默认是10240 一旦大于将会把临时文件存入到临时目录。-->
        <property name="maxInMemorySize" value="104857600"/>
    </bean>

    <!--指定上传过程中解析到的临时文件存储在D:\springmvcfilepath下-->
    <bean id="fileSystemResource" class="org.springframework.core.io.FileSystemResource">
        <constructor-arg name="path" value="D:\springmvcfilepath"/>
    </bean>

                    配置好了我们写一个Controller来测试一下:

    @RequestMapping(value = "upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    private String upload(MultipartFile file, String fileName){
        //使用类型是MultipartFile来接受文件
        System.out.println(file.getOriginalFilename());
        System.out.println(fileName);
        return "register";
    }

                   测试:

                     测试的注意点1:必须使用POST请求,这个是SpringMVC的强制要求,后面看源码的时候会有体现。

                     测试的注意点2:必须使用multipart/form-data的方式提交请求。

                     测试的注意点3:尽量在请求头里添加Content-Type=multipart/form-data

                     使用postMan测试截图:

                            1、请求体的方式:

                                           

                            2、请求头的设置方式:

                                          

                           3、请求成功样例:

                                 

 

2、我们对MultipartResolver的整个配置以及使用方式做了详细的演示,接下来我们剖析整个流程的源码:

      2,1、我们主要剖析的是演示的CommonsMultipartResolver源码.:

         步骤1:DispatcherServlet中检查是否是文件上传请求源码:

	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
                如果DispatcherServlet的multipartResolver解析器存在,且解析器解析了请求是文件上传请求。那就去解析请求的文件内容。
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
				logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
						"this typically results from an additional MultipartFilter in web.xml");
			}
			else if (hasMultipartException(request) ) {
				logger.debug("Multipart resolution failed for current request before - " +
						"skipping re-resolution for undisturbed error rendering");
			}
			else {
				try {
                                        解析请求的文件内容。
					return this.multipartResolver.resolveMultipart(request);
				}
				catch (MultipartException ex) {
					if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
						logger.debug("Multipart resolution failed for error dispatch", ex);
						// Keep processing error dispatch with regular request handle below
					}
					else {
						throw ex;
					}
				}
			}
		}
		// If not returned before: return original request.
		return request;
	}

                   判断是否是多部份请求:在CommonsMultipartResolver中:

	@Override
	public boolean isMultipart(HttpServletRequest request) {
                使用ServletFileUpload来进行判断,这个ServletFileUpload就是commons-fileupdload包里面的实现。
		return (request != null && ServletFileUpload.isMultipartContent(request));
	}

                   使用Apache Commons FileUpload包里的ServletFileUpload的isMultipartContent方法来判断是否是文件上传的请求:

       public static final boolean isMultipartContent(
            HttpServletRequest request) {
           如果请求方式不是POST那就不是文件上传请求,也就是说之只支持POST请求上传文件。
           if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
              return false;
           }
           使用FileUploadBase来判断是否是文件上传请求,FileUploadBase也是apache commons fileupload包里的类
           return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
       }

                   使用Apache Commons FileUpload包里的FileUploadBase的isMultipartContent方法来判断是否是文件上传的请求:

           public static final boolean isMultipartContent(RequestContext ctx) {
             获取请求的的Content-Type
             String contentType = ctx.getContentType();
             if (contentType == null) {
                如果没获取到,那就直接返回当前请求不是文件上传请求。
                return false;
             }
             如果获取到的Content-Type是以“multipart/”开头的,那就表示是文件上传请求。
             if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
                return true;
             }
             return false;
           }

                   接下来,如果判断请求是文件上传请求的话,就使用multipartResolver来解析请求,在CommonsMultipartResolver的resolveMultipart方法中:

	@Override
	public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
		Assert.notNull(request, "Request must not be null");
		if (this.resolveLazily) {
		      如果配置了CommonsMultipartResolver解析器延迟解析文件上传请求,那就构建的一个默认
              的文件上传请求DefaultMultipartHttpServletRequest实例返回,然后在
              HandlerAdapter处理器适配器执行处理器的时候,给处理器的方法入参绑定参数的时候会调
              用其initializeMultipart方法去解析请求中的文件数据。
			return new DefaultMultipartHttpServletRequest(request) {
				@Override
				protected void initializeMultipart() {
					MultipartParsingResult parsingResult = parseRequest(request);
					setMultipartFiles(parsingResult.getMultipartFiles());
					setMultipartParameters(parsingResult.getMultipartParameters());
					setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
				}
			};
		}
		else {
		        如果配置了CommonsMultipartResolver解析器不需要延迟文件的解析,那就立即解析请求
                        中的文件,解析的详细比较复杂我们不过多讲解,感兴趣的可以自己无跟一下源
                        码。在这里就会将文件存入到配置的临时文件目录下,当请求结束后会删除此临时文件。
			MultipartParsingResult parsingResult = parseRequest(request);

			然后使用解析好的文件等数据构建一个默认的文件上传请求DefaultMultipartHttpServletRequest实例返回。
			return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
					parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
		}
	}

                    以上就是整个类为CommonsMultipartResolver的文件上传解析的工作流程,至于在延迟解析的部分我们会在讲解HandlerAdapter的时候再剖析。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值