当前位置: 代码迷 >> 综合 >> spring基本使用(20)-springMVC4-SpringMVC九大组件之文件上传解析器MultipartResolver
  详细解决方案

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

热度:104   发布时间:2023-10-24 15:43:16.0

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中:

	@Overridepublic 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-TypeString 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方法中:

	@Overridepublic 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) {@Overrideprotected 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的时候再剖析。

 

 

 

 

  相关解决方案