Spring+SpringMVC实现文件上传&文件上传源码解析&文件上传原理总结


前言

本文章主要目的在于解析SpringMVC文件上传的CommonsMultipartFileResolver解析器及MultipartFile封装过程


一、Spirng集成SpringMVC实现文件上传

如果大家记得SpringMVC上传文件的代码步骤,那就忽略该环节,直接先第二个。源码解析

环境搭建

  • 第一步:添加maven依赖
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>org.example</groupId>
   <artifactId>SpringCommonsMultifile</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>war</packaging>

   <name>Spring整合SpringMVC文件上传</name>

   <dependencies>
       <!-- Spring依赖 -->
       <!-- 1.Spring核心依赖 -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-core</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-beans</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <!-- 2.Spring dao依赖 -->
       <!-- spring-jdbc包括了一些如jdbcTemplate的工具类 -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-jdbc</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-tx</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <!-- 3.Spring web依赖 -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-web</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>
       <!-- 4.Spring test依赖:方便做单元测试和集成测试 -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-test</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>

       <!-- spring mvc 框架 -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>4.3.7.RELEASE</version>
       </dependency>

       <!-- servlet -->
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>servlet-api</artifactId>
           <version>2.5</version>
       </dependency>

       <!-- jsp/jstl/core 页面标签 -->
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>jstl</artifactId>
           <version>1.2</version>
       </dependency>
       <dependency>
           <groupId>taglibs</groupId>
           <artifactId>standard</artifactId>
           <version>1.1.2</version>
       </dependency>
       <!-- SLF4J API -->
       <!-- SLF4J 是一个日志抽象层,允许你使用任何一个日志系统,并且可以随时切换还不需要动到已经写好的程序 -->
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>1.7.22</version>
       </dependency>

       <!-- Log4j 日志系统(最常用) -->
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
           <version>1.7.22</version>
       </dependency>

       <!-- jackson -->
       <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-core</artifactId>
           <version>2.11.3</version>
       </dependency>

       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-annotations</artifactId>
           <version>2.11.3</version>
       </dependency>
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-databind</artifactId>
           <version>2.11.3</version>
       </dependency>
       <dependency>
           <groupId>commons-fileupload</groupId>
           <artifactId>commons-fileupload</artifactId>
           <version>1.3.3</version>
       </dependency>
   </dependencies>
</project>
  • 第二步:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
   <!-- 配置自动扫描的包 -->
   <context:component-scan base-package="com.spring">
       <!-- 扫描时跳过 @Controller 注解的JAVA类(控制器) -->
       <context:exclude-filter type="annotation"
                               expression="org.springframework.stereotype.Controller"/>
   </context:component-scan>
</beans>
  • spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="com.spring">
        <!-- 扫描时跳过 @Controller 注解的JAVA类(控制器) -->
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>
  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
       "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
       "http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        id="WebApp_ID" version="3.0">

   <display-name>Spring整合SpringMVC文件上传</display-name>
   <context-param>
       <param-name>contextConfigLocation </param-name>
       <param-value>classpath:applicationContext.xml</param-value>
   </context-param>
   <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
   <servlet>
     <servlet-name>dispatcherServlet</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:spring-mvc.xml</param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
     <servlet-name>dispatcherServlet</servlet-name>
     <url-pattern>/</url-pattern>
   </servlet-mapping>
   <filter>
     <filter-name>characterEncodingFilter</filter-name>
     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
     <init-param>
       <param-name>encoding</param-name>
       <param-value>UTF-8</param-value>
     </init-param>
   </filter>
   <filter-mapping>
     <filter-name>characterEncodingFilter</filter-name>
     <url-pattern>/</url-pattern>
   </filter-mapping>
   <welcome-file-list>
       <welcome-file>index.jsp</welcome-file>
   </welcome-file-list>
</web-app>
  • fileUpload.html上传页面
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>spring集成SpringMVC实现文件上传</title>
</head>
<body>
   <form method="post" action="/sys/commons/file/upload" enctype="multipart/form-data">
       <input type="file" name="file" formenctype="multipart/form-data"/>
       <input type="submit"/>
   </form>
</body>
</html>
  • Controller
package com.spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("sys/commons/file")
public class SysFileCommonsController {

  @RequestMapping("upload")
  @ResponseBody
  public Map<String,Object> upload(@RequestParam("file") MultipartFile file){
      Map<String,Object> result = new HashMap<String, Object>();
      File newFile = new File("C:\\Users\\dell\\Desktop\\test.jpg");
      try {
          file.transferTo(newFile);
          result.put("code",200);
          result.put("filePath","C:\\Users\\dell\\Desktop\\test.jpg");
      } catch (IOException e) {
          e.printStackTrace();
          result.put("code",500);
          result.put("Error",e.getMessage());
      }
      return result;
  }

  @RequestMapping("test")
  @ResponseBody
  public Map<String,Object> test(){
      Map<String,Object> result = new HashMap<String, Object>();
      result.put("msg","你好,Spring老兄,我们又见面了");
      return result;
  }
}

最终能结果如下图:
在这里插入图片描述

二、CommonsMultipartResolver原理及源码解析

老规矩,鲁迅先生曾经说过:分析原理不画结构图,那是麻绳提豆腐一别提了。
好了,废话少说:先上图
在这里插入图片描述

结合上面的结构图我们来一步一步讲解起源

  • 节点一:当进入DispathcherServlet中后会调用doDispatch方法,代码如下
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       HttpServletRequest processedRequest = request;
       HandlerExecutionChain mappedHandler = null;
       boolean multipartRequestParsed = false;
       WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

       try {
           try {
               ModelAndView mv = null;
               Object dispatchException = null;

               try {
                   /**
                    *这里调用了checkMultipart方法,并且返回一个HttpServletRequest对象
                    *返回的这个对像就是已经封装好文件数据的对象
                    */
                   processedRequest = this.checkMultipart(request);
                   multipartRequestParsed = processedRequest != request;
                   mappedHandler = this.getHandler(processedRequest);
                   if (mappedHandler == null || mappedHandler.getHandler() == null) {
                       this.noHandlerFound(processedRequest, response);
                       return;
                   }

                   HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                   String method = request.getMethod();
                   boolean isGet = "GET".equals(method);
                   if (isGet || "HEAD".equals(method)) {
                       long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                       if (this.logger.isDebugEnabled()) {
                           this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                       }

                       if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                           return;
                       }
                   }

                   if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                       return;
                   }

                   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                   if (asyncManager.isConcurrentHandlingStarted()) {
                       return;
                   }

                   this.applyDefaultViewName(processedRequest, mv);
                   mappedHandler.applyPostHandle(processedRequest, response, mv);
               } catch (Exception var20) {
                   dispatchException = var20;
               } catch (Throwable var21) {
                   dispatchException = new NestedServletException("Handler dispatch failed", var21);
               }

               this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
           } catch (Exception var22) {
               this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
           } catch (Throwable var23) {
               this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
           }

       } finally {
           if (asyncManager.isConcurrentHandlingStarted()) {
               if (mappedHandler != null) {
                   mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
               }
           } else if (multipartRequestParsed) {
               this.cleanupMultipart(processedRequest);
           }

       }
   }

在doDispatch方法中我们可以看到,它将request交给了checkMultipart方法,并且返回了一个HttpServletRequest对象,其实这个返回的对象中就已经包含了上传的文件信息。那么它是怎么将文件信息封装进来的呢?继续看第二个节点

  • 节点二
    节点二就是checkMultipart方法内部所做的事情
    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
       if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
           if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
               this.logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, this typically results from an additional MultipartFilter in web.xml");
           } else if (this.hasMultipartException(request)) {
               this.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 var3) {
                   if (request.getAttribute("javax.servlet.error.exception") == null) {
                       throw var3;
                   }
               }

               this.logger.debug("Multipart resolution failed for error dispatch", var3);
           }
       }

       return request;
   }

在进入到checkMultipart方法后,首先进行了判断。

1、判断容器中是否存在id为multipartResolver的上传文件解析器commonsMutpartResolver的bean实例
2、判断请求方式是否是POST请求,并且是参数类型是否是以"multipart/"开头的
到这里其实也就是进入到了节点三
具体源码如下

  • 节点三

isMultipart方法

	@Override
   public boolean isMultipart(HttpServletRequest request) {
   	return (request != null && ServletFileUpload.isMultipartContent(request));
   }

目的:request请求不为空的情况下继续判断请求是否符合带有文件的请求

isMultipartContent方法如下

    public static final boolean isMultipartContent(HttpServletRequest request) {
       return !"POST".equalsIgnoreCase(request.getMethod()) ? false : FileUploadBase.isMultipartContent(new ServletRequestContext(request));
   }

isMultipartContent方法如下

    public static final boolean isMultipartContent(RequestContext ctx) {
       String contentType = ctx.getContentType();
       if (contentType == null) {
           return false;
       } else {
           return contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/");
       }
   }

可以看上面的方法先判断了请求是否是POST请求,然后又判断了请求的内容类型是否是以"multipart/"开头
经过以上三个方法的层层调用,最终节点三返回了一个布尔值

  • 节点四
    当节点返回的是true是,才会继续进入第四个节点
    并且调用CommonsMultipartResolver对象的resolveMultipart方法,这时候才会真正的返回一个带有上传文件信息的HttpServletRequest对象
	@Override
   public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
   	Assert.notNull(request, "Request must not be null");
   	if (this.resolveLazily) {
   		/**
   		  *执行到这里是才真正的开始创建DefaultMultipartHttpServletRequest对象,
   		  *并且复写了的它的初始化方法,进而径文件信息封装到了这个对象中
   		  */
   		return new DefaultMultipartHttpServletRequest(request) {
   			@Override
   			protected void initializeMultipart() {
   				MultipartParsingResult parsingResult = parseRequest(request);
   				setMultipartFiles(parsingResult.getMultipartFiles());
   				setMultipartParameters(parsingResult.getMultipartParameters());
   				setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
   			}
   		};
   	}
   	else {
   		MultipartParsingResult parsingResult = parseRequest(request);
   		return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
   				parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
   	}
   }

将文件信息封装到HttpServletRequest中,并且将其返回给DispathcherServlet对象
看到这里可能有同学要问了,resolveMultipart方法返回的是DefaultMultipartHttpServletRequest对象,数据怎么跑到DispathcherServlet去了。
别慌,看一下下面的集成关系图你就懂了
在这里插入图片描述怎么样,够清楚了吧。ServletRequest是它的老祖先,拿他点数据还不行吗,难道他还有意见不成!
在这里插入图片描述


总结

SpringMVC文件上传原理

当客户端发送了带有文件信息并且ContentType是以"multipart/"开头的请求时,DispathcherServlet前端控制器会将请求委托给CommonsMutipartResolver文件解析器,文件解析器分析请求信息是否为POST请求并且请求中是否含有ContentType以"multipart/"开头的数据,当它们都为true时,前端控制器才会继续委托CommonsMultipart文件解析器将请求中的文件数据封装到DefaultMultipartHttpServletRequest对象中,并且最终返回给DispathcherServlet

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值