Spring MVC 源码之MultipartResolver 组件

news2024/9/21 2:45:57
MultipartResolver 组件,内容类型( Content-Type )为 multipart/* 的请求的解析器,主要解析文件上传的请求。例如, MultipartResolver 会将 HttpServletRequest 封装成 MultipartHttpServletRequest 对象,便于获取参数信息以及上传的文件
关于在 SpringBoot 中如何使用文件上传可参考 Spring 官方文档

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 MultipartResolver 组件,可以回到《 一次请求响应的过程》中的 DispatcherServletdoDispatch 方法中看看,如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ... 省略相关代码
    // <2> 检测请求是否为上传请求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象
    processedRequest = checkMultipart(request);
    // ... 省略相关代码
}

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 如果该请求是一个涉及到 multipart (文件)的请求
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                // 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
                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;
}

<2> 处,如果该请求是一个涉及到 multipart (文件)的请求,则通过 multipartResolverHttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件

MultipartResolver接口

org.springframework.web.multipart.MultipartResolver 接口,内容类型( Content-Type )为 multipart/* 的请求的解析器接口,代码如下:

public interface MultipartResolver {
    /**
     * 是否为 multipart 请求
     */
    boolean isMultipart(HttpServletRequest request);
    /**
     * 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象
     */
    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

    /**
     * 清理处理 multipart 产生的资源,例如临时文件
     */
    void cleanupMultipart(MultipartHttpServletRequest request);
}

MultipartResolver 接口体系的结构如下:

一共有两块:

  • 上半部分,MultipartRequest 接口及其实现类

  • 下半部分,MultipartResolver 接口以及其实现类

初始化过程

DispatcherServletinitMultipartResolver(ApplicationContext context) 方法,初始化 MultipartResolver 组件,方法如下:

private void initMultipartResolver(ApplicationContext context) {
    try {
        // 从 Spring 上下文中获取名称为 "multipartResolver" ,类型为 MultipartResolver 的 Bean
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.multipartResolver);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
        if (logger.isTraceEnabled()) {
            logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
        }
    }
}
  • 在 Spring MVC 中,multipartResolver 默认为 null【注意】,需要自己配置,mybatis 集成 Spring 模块下的 spring-mvc.xml 文件中配置 MultipartResolver 为 CommonsMultipartResolver 实现类,也可以配置为 StandardServletMultipartResolver 实现类

  • 在 Spring Boot 中,multipartResolver 默认为 StandardServletMultipartResolver 实现类

目前 Spring 只提供上面两种实现类,接下来依次进行分析

StandardServletMultipartResolver

org.springframework.web.multipart.support.StandardServletMultipartResolver,实现 MultipartResolver 接口,基于 Servlet 3.0 标准的上传文件 API 的 MultipartResolver 实现类,代码如下:

public class StandardServletMultipartResolver implements MultipartResolver {

   /**
    * 是否延迟解析
    */
   private boolean resolveLazily = false;

   public void setResolveLazily(boolean resolveLazily) {
      this.resolveLazily = resolveLazily;
   }


   @Override
   public boolean isMultipart(HttpServletRequest request) {
      // 请求的 Content-type 必须 multipart/ 开头
      return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
   }

   @Override
   public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
      return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
   }

   @Override
   public void cleanupMultipart(MultipartHttpServletRequest request) {
      if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) {
         // To be on the safe side: explicitly delete the parts,
         // but only actual file parts (for Resin compatibility)
         try {
            // 删除临时的 Part
            for (Part part : request.getParts()) {
               if (request.getFile(part.getName()) != null) {
                  part.delete();
               }
            }
         }
         catch (Throwable ex) {
            LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
         }
      }
   }

}
  • isMultipart(HttpServletRequest request)方法,请求的 Content-type 是否以 multipart/ 开头

  • resolveMultipart(HttpServletRequest request)方法,直接将 HttpServletRequest 转换成 StandardMultipartHttpServletRequest 对象

  • cleanupMultipart(MultipartHttpServletRequest request)方法,清理资源,删除临时的 javax.servlet.http.Part

StandardMultipartHttpServletRequest

org.springframework.web.multipart.support.StandardMultipartHttpServletRequest,继承 AbstractMultipartHttpServletRequest 抽象类,基于 Servlet 3.0 的 Multipart HttpServletRequest 实现类,包含了一个 javax.servlet.http.HttpServletRequest 对象和它的 javax.servlet.http.Part 对象们,其中 Part 对象会被封装成 StandardMultipartFile 对象

public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
    /**
     * 普通参数名的集合
     */
    @Nullable
    private Set<String> multipartParameterNames;

    public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException {
        this(request, false);
    }

    public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
        super(request);
        // 如果不需要延迟解析
        if (!lazyParsing) {
            // 解析请求
            parseRequest(request);
        }
    }
}
  • multipartParameterNames:普通参数名的集合,非上传文件的参数名

  • 如果不需要延迟解析,则调用 parseRequest(HttpServletRequest request) 方法,直接解析请求

parseRequest

parseRequest(HttpServletRequest request) 方法,解析请求,解析 HttpServletRequest 中的 Part 对象,如果是文件,则封装成 StandardMultipartFile 对象,否则就是普通参数,获取其名称,如下:

private void parseRequest(HttpServletRequest request) {
    try {
        // <1> 从 HttpServletRequest 中获取 Part 们
        Collection<Part> parts = request.getParts();
        this.multipartParameterNames = new LinkedHashSet<>(parts.size());
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
        // <2> 遍历 parts 数组
        for (Part part : parts) {
            // <2.1> 获得请求头中的 Content-Disposition 信息,MIME 协议的扩展
            String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
            // <2.2> 对 Content-Disposition 信息进行解析,生成 ContentDisposition 对象
            // 包含请求参数信息,以面向“对象”的形式进行访问
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            // <2.3> 获得文件名
            String filename = disposition.getFilename();
            // <2.4> 情况一,文件名非空,说明是文件参数,则创建 StandardMultipartFile 对象
            if (filename != null) {
                if (filename.startsWith("=?") && filename.endsWith("?=")) {
                    filename = MimeDelegate.decode(filename);
                }
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            }
            // <2.5> 情况二,文件名为空,说明是普通参数,则保存参数名称
            else {
                this.multipartParameterNames.add(part.getName());
            }
        }
        // <3> 将上面生成的 StandardMultipartFile 文件对象们,设置到父类的 multipartFiles 属性中
        setMultipartFiles(files);
    }
    catch (Throwable ex) {
        handleParseFailure(ex);
    }
}
  1. 从 HttpServletRequest 中获取 Part 们

  1. 遍历 parts 数组

  1. 从 Part 对象中获得请求头中的 Content-Disposition 信息,MIME 协议的扩展

  1. 对 Content-Disposition 信息进行解析,生成 ContentDisposition 对象,包含请求参数信息,以面向“对象”的形式进行访问

  1. ContentDisposition 对象中获得文件名

  1. 情况一,文件名非空,说明是文件参数,则创建 StandardMultipartFile 对象

  1. 情况二,文件名为空,说明是普通参数,则保存参数名称

  1. 将上面生成的 StandardMultipartFile 文件对象们,设置到父类的 multipartFiles 属性中

  1. 如果发生异常则抛出

其他方法

/** 初始化请求 */
@Override
protected void initializeMultipart() {
    parseRequest(getRequest());
}
/** 获取请求中的参数名称 */
@Override
public Enumeration<String> getParameterNames() {
    if (this.multipartParameterNames == null) {
        initializeMultipart();
    }
    if (this.multipartParameterNames.isEmpty()) {
        return super.getParameterNames();
    }

    // Servlet 3.0 getParameterNames() not guaranteed to include multipart form items
    // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
    Set<String> paramNames = new LinkedHashSet<>();
    Enumeration<String> paramEnum = super.getParameterNames();
    while (paramEnum.hasMoreElements()) {
        paramNames.add(paramEnum.nextElement());
    }
    paramNames.addAll(this.multipartParameterNames);
    return Collections.enumeration(paramNames);
}
/** 获取请求中的参数,参数名和参数值的映射 */
@Override
public Map<String, String[]> getParameterMap() {
    if (this.multipartParameterNames == null) {
        initializeMultipart();
    }
    if (this.multipartParameterNames.isEmpty()) {
        return super.getParameterMap();
    }
    // Servlet 3.0 getParameterMap() not guaranteed to include multipart form items
    // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
    Map<String, String[]> paramMap = new LinkedHashMap<>(super.getParameterMap());
    for (String paramName : this.multipartParameterNames) {
        if (!paramMap.containsKey(paramName)) {
            paramMap.put(paramName, getParameterValues(paramName));
        }
    }
    return paramMap;
}
/** 获取请求的 Content-Type 内容类型 */
@Override
public String getMultipartContentType(String paramOrFileName) {
    try {
        Part part = getPart(paramOrFileName);
        return (part != null ? part.getContentType() : null);
    }
    catch (Throwable ex) {
        throw new MultipartException("Could not access multipart servlet request", ex);
    }
}
/** 获取请求头信息 */
@Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
    try {
        Part part = getPart(paramOrFileName);
        if (part != null) {
            HttpHeaders headers = new HttpHeaders();
            for (String headerName : part.getHeaderNames()) {
                headers.put(headerName, new ArrayList<>(part.getHeaders(headerName)));
            }
            return headers;
        }
        else {
            return null;
        }
    }
    catch (Throwable ex) {
        throw new MultipartException("Could not access multipart servlet request", ex);
    }
}

StandardMultipartFile

org.springframework.web.multipart.support.StandardMultipartHttpServletRequest 的私有内部静态类,实现了 MultipartFile 接口和 Serializable 接口,内部封装了 javax.servlet.http.Part 对象和文件名称,代码如下:

private static class StandardMultipartFile implements MultipartFile, Serializable {

    private final Part part;

    private final String filename;

    public StandardMultipartFile(Part part, String filename) {
        this.part = part;
        this.filename = filename;
    }

    @Override
    public String getName() {
        return this.part.getName();
    }

    @Override
    public String getOriginalFilename() {
        return this.filename;
    }

    @Override
    public String getContentType() {
        return this.part.getContentType();
    }

    @Override
    public boolean isEmpty() {
        return (this.part.getSize() == 0);
    }

    @Override
    public long getSize() {
        return this.part.getSize();
    }

    @Override
    public byte[] getBytes() throws IOException {
        return FileCopyUtils.copyToByteArray(this.part.getInputStream());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return this.part.getInputStream();
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        this.part.write(dest.getPath());
        if (dest.isAbsolute() && !dest.exists()) {
            // Servlet 3.0 Part.write is not guaranteed to support absolute file paths:
            // may translate the given path to a relative location within a temp dir
            // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths).
            // At least we offloaded the file from memory storage; it'll get deleted
            // from the temp dir eventually in any case. And for our user's purposes,
            // we can manually copy it to the requested location as a fallback.
            FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));
        }
    }

    @Override
    public void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest));
    }
}

这个类封装了 Servlet 3.0 的 Part 对象,也就是我们常用到的 MultipartFile 对象,支持对文件的操作,内部其实都是调用 javax.servlet.http.Part 的方法

AbstractMultipartHttpServletRequest

org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest 抽象类,继承了 HttpServletRequestWrapper 类,实现了 MultipartHttpServletRequest接口

该类是 StandardMultipartHttpServletRequestDefaultMultipartHttpServletRequest 的父类,实现了一些公共的方法,代码如下:

public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest {
    /**
     * 请求中的文件信息
     */
    @Nullable
    private MultiValueMap<String, MultipartFile> multipartFiles;

    protected AbstractMultipartHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public HttpServletRequest getRequest() {
        return (HttpServletRequest) super.getRequest();
    }

    @Override
    public HttpMethod getRequestMethod() {
        return HttpMethod.resolve(getRequest().getMethod());
    }

    /** 获取请求头信息 */
    @Override
    public HttpHeaders getRequestHeaders() {
        HttpHeaders headers = new HttpHeaders();
        Enumeration<String> headerNames = getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, Collections.list(getHeaders(headerName)));
        }
        return headers;
    }

    /** 获取文件名称列表 */
    @Override
    public Iterator<String> getFileNames() {
        return getMultipartFiles().keySet().iterator();
    }

    /** 获取指定文件名的单个文件 */
    @Override
    public MultipartFile getFile(String name) {
        return getMultipartFiles().getFirst(name);
    }

    /** 获取指定文件名的多个文件 */
    @Override
    public List<MultipartFile> getFiles(String name) {
        List<MultipartFile> multipartFiles = getMultipartFiles().get(name);
        if (multipartFiles != null) {
            return multipartFiles;
        }
        else {
            return Collections.emptyList();
        }
    }

    @Override
    public Map<String, MultipartFile> getFileMap() {
        return getMultipartFiles().toSingleValueMap();
    }

    @Override
    public MultiValueMap<String, MultipartFile> getMultiFileMap() {
        return getMultipartFiles();
    }

    public boolean isResolved() {
        return (this.multipartFiles != null);
    }

    protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
        this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles));
    }

    protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
        if (this.multipartFiles == null) {
            initializeMultipart();
        }
        return this.multipartFiles;
    }

    /** 交由子类实现 */
    protected void initializeMultipart() {
        throw new IllegalStateException("Multipart request not initialized");
    }
}

上面的方法都比较简单,用于获取请求中的文件对象

MultiValueMap<String, MultipartFile> multipartFiles属性,保存由子类解析出请求中的 Part 对象所封装成的 MultipartFile 对象

CommonsMultipartResolver

org.springframework.web.multipart.commons.CommonsMultipartResolver,实现 MultipartResolver、ServletContextAware 接口,继承 CommonsFileUploadSupport 抽象类,基于 Apache Commons FileUpload 的 MultipartResolver 实现类

如果需要使用这个 MultipartResolver 实现类,需要引入 commons-fileuploadcommons-iocommons-codec 组件,例如:

<dependencies>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
</dependencies>

注意,如果 Spring Boot 项目中需要使用 CommonsMultipartResolver,需要在 application.yml 中添加如下配置,排除其默认的配置,如下:

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration

构造方法

public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware {
    /**
     * 是否延迟解析
     */
    private boolean resolveLazily = false;

    public CommonsMultipartResolver() {
        super();
    }

    public CommonsMultipartResolver(ServletContext servletContext) {
        this();
        setServletContext(servletContext);
    }
}

isMultipart

@Override
public boolean isMultipart(HttpServletRequest request) {
    // 必须是 POST 请求,且 Content-Type 为 multipart/ 开头
    return ServletFileUpload.isMultipartContent(request);
}

判断是否为 multipart 请求,必须是 POST 请求,且 Content-Type 为 multipart/ 开头

resolveMultipart

@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
    Assert.notNull(request, "Request must not be null");
    if (this.resolveLazily) {
        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 转换成 DefaultMultipartHttpServletRequest 对象

如果开启了延迟解析,则重写该对象的 initializeMultipart() 方法,用于解析请求

否则直接调用 parseRequest(HttpServletRequest request) 方法解析请求,返回 MultipartParsingResult 对象,包含 MultipartFile 对象和普通参数信息

parseRequest

parseRequest(HttpServletRequest request)方法,用于解析请求,返回 MultipartParsingResult 对象,包含 MultipartFile 对象、普通参数信息以及参数的 Content-Type 信息,方法如下:

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
    // <1> 获取请求中的编码
    String encoding = determineEncoding(request);
    // <2> 获取 ServletFileUpload 对象
    FileUpload fileUpload = prepareFileUpload(encoding);
    try {
        // <3> 获取请求中的流数据
        List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
        // <4> 将这些流数据转换成 MultipartParsingResult,包含 CommonsMultipartFile、参数信息、Content-type
        return parseFileItems(fileItems, encoding);
    }
    catch (FileUploadBase.SizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
    }
    catch (FileUploadBase.FileSizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
    }
    catch (FileUploadException ex) {
        throw new MultipartException("Failed to parse multipart servlet request", ex);
    }
}
  1. 获取请求中的编码

  1. 根据编码获取到 ServletFileUpload 对象( commons-fileupload 中的类),在 newFileUpload(FileItemFactory fileItemFactory) 方法中返回的就是 ServletFileUpload 对象,可以看到父类 CommonsFileUploadSupport 的构造方法,如下:

// org.springframework.web.multipart.commons.CommonsFileUploadSupport.java

public CommonsFileUploadSupport() {
    this.fileItemFactory = newFileItemFactory();
    // 由子类实现
    this.fileUpload = newFileUpload(getFileItemFactory());
}
  1. 具体细节就不讲述了

  1. 通过 ServletFileUpload 对象解析请求,返回流数据 List<FileItem> fileItems

  1. 调用父类 CommonsFileUploadSupport 的 parseFileItems(List<FileItem> fileItems, String encoding) 方法,将这些流数据转换成 MultipartParsingResult 对象

// org.springframework.web.multipart.commons.CommonsFileUploadSupport.java

protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
    MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
    Map<String, String[]> multipartParameters = new HashMap<>();
    Map<String, String> multipartParameterContentTypes = new HashMap<>();

    // Extract multipart files and multipart parameters.
    for (FileItem fileItem : fileItems) {
        if (fileItem.isFormField()) {
            String value;
            String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
            try {
                value = fileItem.getString(partEncoding);
            }
            catch (UnsupportedEncodingException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
                            "' with encoding '" + partEncoding + "': using platform default");
                }
                value = fileItem.getString();
            }
            String[] curParam = multipartParameters.get(fileItem.getFieldName());
            if (curParam == null) {
                // simple form field
                multipartParameters.put(fileItem.getFieldName(), new String[] {value});
            }
            else {
                // array of simple form fields
                String[] newParam = StringUtils.addStringToArray(curParam, value);
                multipartParameters.put(fileItem.getFieldName(), newParam);
            }
            multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
        }
        else {
            // multipart file field
            CommonsMultipartFile file = createMultipartFile(fileItem);
            multipartFiles.add(file.getName(), file);
            LogFormatUtils.traceDebug(logger, traceOn ->
                    "Part '" + file.getName() + "', size " + file.getSize() +
                            " bytes, filename='" + file.getOriginalFilename() + "'" +
                            (traceOn ? ", storage=" + file.getStorageDescription() : "")
            );
        }
    }
    return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}

大致就是遍历 fileItems 集合,如果是一个简单的表单字段,那么就是一个普通的参数,将参数名和值保存起来

否则就是文件,将其封装成 CommonsMultipartFile 保存起来

cleanupMultipart

cleanupMultipart(MultipartHttpServletRequest request)方法,清理文件产生的临时资源,如下:

// CommonsMultipartResolver.java
@Override
public void cleanupMultipart(MultipartHttpServletRequest request) {
    if (!(request instanceof AbstractMultipartHttpServletRequest) ||
            ((AbstractMultipartHttpServletRequest) request).isResolved()) {
        try {
            cleanupFileItems(request.getMultiFileMap());
        }
        catch (Throwable ex) {
            logger.warn("Failed to perform multipart cleanup for servlet request", ex);
        }
    }
}
// CommonsFileUploadSupport.java
protected void cleanupFileItems(MultiValueMap<String, MultipartFile> multipartFiles) {
    for (List<MultipartFile> files : multipartFiles.values()) {
        for (MultipartFile file : files) {
            if (file instanceof CommonsMultipartFile) {
                CommonsMultipartFile cmf = (CommonsMultipartFile) file;
                cmf.getFileItem().delete();
                LogFormatUtils.traceDebug(logger, traceOn -> "Cleaning up part '..."));
            }
        }
    }
}

DefaultMultipartHttpServletRequest

org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest,继承 AbstractMultipartHttpServletRequest 抽象类,MultipartHttpServletRequest 的默认实现类,代码如下:

public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {

    private static final String CONTENT_TYPE = "Content-Type";

    @Nullable
    private Map<String, String[]> multipartParameters;

    @Nullable
    private Map<String, String> multipartParameterContentTypes;

    public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles,
            Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {

        super(request);
        setMultipartFiles(mpFiles);
        setMultipartParameters(mpParams);
        setMultipartParameterContentTypes(mpParamContentTypes);
    }

    public DefaultMultipartHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    @Nullable
    public String getParameter(String name) {
        String[] values = getMultipartParameters().get(name);
        if (values != null) {
            return (values.length > 0 ? values[0] : null);
        }
        return super.getParameter(name);
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] parameterValues = super.getParameterValues(name);
        String[] mpValues = getMultipartParameters().get(name);
        if (mpValues == null) {
            return parameterValues;
        }
        if (parameterValues == null || getQueryString() == null) {
            return mpValues;
        }
        else {
            String[] result = new String[mpValues.length + parameterValues.length];
            System.arraycopy(mpValues, 0, result, 0, mpValues.length);
            System.arraycopy(parameterValues, 0, result, mpValues.length, parameterValues.length);
            return result;
        }
    }

    @Override
    public Enumeration<String> getParameterNames() {
        Map<String, String[]> multipartParameters = getMultipartParameters();
        if (multipartParameters.isEmpty()) {
            return super.getParameterNames();
        }

        Set<String> paramNames = new LinkedHashSet<>();
        paramNames.addAll(Collections.list(super.getParameterNames()));
        paramNames.addAll(multipartParameters.keySet());
        return Collections.enumeration(paramNames);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> result = new LinkedHashMap<>();
        Enumeration<String> names = getParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            result.put(name, getParameterValues(name));
        }
        return result;
    }

    @Override
    public String getMultipartContentType(String paramOrFileName) {
        MultipartFile file = getFile(paramOrFileName);
        if (file != null) {
            return file.getContentType();
        }
        else {
            return getMultipartParameterContentTypes().get(paramOrFileName);
        }
    }

    @Override
    public HttpHeaders getMultipartHeaders(String paramOrFileName) {
        String contentType = getMultipartContentType(paramOrFileName);
        if (contentType != null) {
            HttpHeaders headers = new HttpHeaders();
            headers.add(CONTENT_TYPE, contentType);
            return headers;
        }
        else {
            return null;
        }
    }

    protected final void setMultipartParameters(Map<String, String[]> multipartParameters) {
        this.multipartParameters = multipartParameters;
    }

    protected Map<String, String[]> getMultipartParameters() {
        if (this.multipartParameters == null) {
            initializeMultipart();
        }
        return this.multipartParameters;
    }

    protected final void setMultipartParameterContentTypes(Map<String, String> multipartParameterContentTypes) {
        this.multipartParameterContentTypes = multipartParameterContentTypes;
    }

    protected Map<String, String> getMultipartParameterContentTypes() {
        if (this.multipartParameterContentTypes == null) {
            initializeMultipart();
        }
        return this.multipartParameterContentTypes;
    }
}

代码并不复杂,稍微阅读一下就理解了

总结

本文对 Spring MVC 处理请求的过程中使用到的 MultipartResolver 组件进行了分析,如果请求的 Content-Typemultipart/*,涉及到文件上传,所以处理请求的第一步需要通过 MultipartResolver 组件对请求进行转换处理。会将 HttpServletRequest 请求对象封装成 MultipartHttpServletRequest 对象,便于获取参数信息和操作上传的文件(MultipartFile 对象)。

MultipartResolver 组件的实现类有两种:

  • org.springframework.web.multipart.support.StandardServletMultipartResolver:实现 MultipartResolver 接口,基于 Servlet 3.0 标准的上传文件 API 的 MultipartResolver 实现类

  • org.springframework.web.multipart.commons.CommonsMultipartResolver:实现 MultipartResolver 接口,基于 Apache Commons FileUpload 的 MultipartResolver 实现类

两者的区别:

  • StandardServletMultipartResolver 会将 HttpServletRequest 封装成 StandardMultipartHttpServletRequest 对象,由 Servlet 3.0 提供 API 获取请求中的 javax.servlet.http.Part 对象,然后进行解析,文件会封装成 StandardMultipartFile 对象

  • CommonsMultipartResolver 会将 HttpServletRequest 封装成 DefaultMultipartHttpServletRequest 对象,由 Apache 的 Commons FileUpload 组件来实现,通过 org.apache.commons.fileupload.servlet.ServletFileUpload 对象获取请求中的 org.apache.commons.fileupload.FileItem 对象,然后进行解析,文件会封装成 CommonsMultipartFile 对象,如何使用可以参考上面的 CommonsMultipartResolver 小节

注意事项:

  • 在 Spring MVC 中,multipartResolver 默认为 null,需要自己配置,例如mybatis 集成 Spring 模块下的 spring-mvc.xml 文件中配置 MultipartResolver 为 CommonsMultipartResolver 实现类,也可以配置为 StandardServletMultipartResolver 实现类

  • 在 Spring Boot 中,multipartResolver 默认为 StandardServletMultipartResolver 实现类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358027.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【NVMEM子系统】二、NVMEM驱动框架

个人主页&#xff1a;董哥聊技术我是董哥&#xff0c;嵌入式领域新星创作者创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01;文章目录1、前言2、驱动框架3、源码目录结构4、用户空间下的目录结构1、前言 NVMEM SUBSYSTEM&#xff0c;该子系…

视频片段怎么做成gif图?快试试这2种方法

动态gif图片作为当下非常常用的表情包&#xff0c;其丰富的内容生动的画面深受大众喜爱。那么&#xff0c;当我们想要将电影或是电视剧中的某一片段做成gif动态图片的时候&#xff0c;要如何操作呢&#xff1f;接下来&#xff0c;给大家分享两招视频转化gif的小窍门–使用【GIF…

【力扣-Python-1】两数之和(easy)

https://leetcode.cn/problems/two-sum/题目描述给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出和为目标值 target 的那两个整数&#xff0c;并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答…

uboot下实现U盘自动升级程序的思路分析(基于USB系统、eMMC系统、FAT32文件系统)

1、常见的升级方式 1.1、应用程序升级 优点&#xff1a;在图形化界面操作&#xff0c;只需要选中升级文件并点击升级即可&#xff0c;操作简单&#xff1b; 缺点&#xff1a;应用程序必须能正常启动&#xff0c;当程序出现bug就不能升级&#xff0c;可靠性差&#xff1b; 总结…

旺店通与金蝶云星空对接集成采购入库单接口

旺店通旗舰奇门与金蝶云星空对接集成采购入库单查询连通销售退货新增V1(12-采购入库单集成方案-P)数据源系统:旺店通旗舰奇门旺店通是北京掌上先机网络科技有限公司旗下品牌&#xff0c;国内的零售云服务提供商&#xff0c;基于云计算SaaS服务模式&#xff0c;以体系化解决方案…

Prometheus集群分布式架构浅析

集群行为是一种常见于自然界中鱼群、鸟群、蜂群等低等群居生物的集体行为&#xff0c;受此启发形成了无人机集群的概念。无人机集群不是多无人机间的简单编队&#xff0c;而是通过必要的控制策略使之产生集群协同效应&#xff0c;从而具备执行复杂多变、危险任务的能力。目前无…

【C++】AVLTree——高度平衡二叉搜索树

文章目录一、AVL树的概念二、AVL树节点的定义三、AVL树的插入四、AVL树的旋转1.左单旋2.右单旋3.左右双旋4.右左双旋五、进行验证六、AVLTree的性能个人简介&#x1f4dd; &#x1f3c6;2022年度博客之星Top18;&#x1f3c6;2022社区之星Top2;的&#x1f947;C/C领域优质创作者…

JVM类加载子系统

1、类加载子系统在内存结构中所处的位置通过内存结构图&#xff0c;我们先知道类加载子系统所处的位置&#xff0c;做到心中有图。2、类加载器作用类加载器子系统负责从文件系统或者网络中加载Class文件&#xff0c;class文件在文件开头有特定的文件标识。ClassLoader只负责cla…

anaconda创建环境为空、修改默认环境位置

无论是用navigator还是命令行创建环境都无法指定python版本conda create -n test python3.9其实就是没有路径&#xff0c;添加几个镜像就好&#xff1a;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels ht…

BUUCTF-[安洵杯 2019]crackMe1

题目下载&#xff1a;下载 这道题涉及到SM4加密和变表base64。 SM4简单了解&#xff1a;SM4算法过程_不是小白才怪的博客-CSDN博客_sm4算法 先运行一下程序&#xff0c; 发现有一个Messagebox&#xff0c;并且内容是hooked。 载入IDA&#xff0c;使用IDA的插件Findcrypt查…

ChatGPT:“抢走你工作的不会是 AI ,而是先掌握 AI 能力的人”

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; ChatGPT&#xff1a;“抢走你工作的不会是 AI &#xff0c;而是先掌握 AI 能力的人” ChatGPT&#xff1a;美国OpenAI 研发的聊天机器人程序&#xff0c;人工智能技术…

Springboot启动过程分析

Springboot启动过程分析 SpringBoot的版本是v3.0.2&#xff0c;下面进行详细的分析。 一、SpringBoot启动流程的主干 示例程序入口如下所示&#xff1a; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApp…

【网络知识】TCP和UDP详解

TCP和UDP 文章目录UDP协议概述TCP协议概述TCP报文段TCP连接的建立两天内完成下面的参考博客&#x1f60a;点此到文末惊喜↩︎ UDP协议 概述 TCP协议 概述 定义 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种传输层通信协议&…

Python 之 Pandas DataFrame 数据类型的简介、创建的列操作

文章目录一、DataFrame 结构简介二、DataFrame 对象创建1. 使用普通列表创建2. 使用嵌套列表创建3 指定数值元素的数据类型为 float4. 字典嵌套列表创建5. 添加自定义的行标签6. 列表嵌套字典创建 DataFrame 对象7. Series 创建 DataFrame 对象三、DataFrame 列操作1. 选取数据…

【LeetCode】剑指 Offer(5)

目录 写在前面&#xff1a; 题目&#xff1a; 题目的接口&#xff1a; 解题思路1&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 解题思路2&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a;…

臻和科技再冲刺港交所上市:近三年亏损14亿元,有股东提前退出

近日&#xff0c;臻和科技集团有限公司&#xff08;下称“臻和科技”&#xff09;再次递交招股书&#xff0c;准备在港交所主板上市。据贝多财经了解&#xff0c;这已经是臻和科技第二次冲刺港交所上市。在此之前&#xff0c;臻和科技曾于2022年9月26日递表&#xff0c;后选择了…

hadoop02【尚硅谷】

HDFS 大数据学习笔记 一、HDFS产出背景及定义 HDFS产生背景 随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;迫切需要一种系统来管理多台机器上的文件&#x…

python基于vue的酒店预约管理平台系统

当用户在上一步中的房间展示界面中点击了房间的图片或者名称之后系统会根据房间的ID自动的跳转到房间的详情页面中来&#xff0c;在房间的详情页面中可以看到房间的图片房间的价格房间的详细介绍房间的类型等内容&#xff0c;当用户登录之后还可以根据需要进行对房间进行预定&a…

Vulnhub靶场之PYLINGTON: 1

1.信息收集 1.输入arp-scan 192.168.239.0/24&#xff0c;探索存活主机&#xff0c;发现主机192.168.239.172存活。 2.对存活主机进行端口扫描&#xff0c;发现22(SSH)、80(Web)端口。 3.访问80端口&#xff0c;在浏览器上输出&#xff1a;http://192.168.239.172。 4.查看…

工具篇3.5世界热力图

一、定义 世界热力图是一种地图形式&#xff0c;它使用颜色的变化来显示世界各个地区的某种指标&#xff08;如 GDP、人口、气候等&#xff09;的分布和密度。通常&#xff0c;世界热力图会使用不同的颜色来表示数据的变化&#xff0c;例如使用蓝色表示低值&#xff0c;红色表…