SpringMVC(十五):Dispatcher的重要组件之一MultipartResolver(StandardServletMultipartResolver和CommonsMultipartResolver)的用法

MultipartResolver组件

从Spring官网上可以看到MultipartResolver接口的定义信息:

public interface MultipartResolver
A strategy interface for multipart file upload resolution in accordance with RFC 1867. Implementations are typically usable both within an application context and standalone.

There are two concrete implementations included in Spring, as of Spring 3.1:

There is no default resolver implementation used for Spring DispatcherServlets, as an application might choose to parse its multipart requests itself. To define an implementation, create a bean with the id "multipartResolver" in a DispatcherServlet's application context. Such a resolver gets applied to all requests handled by that DispatcherServlet.

If a DispatcherServlet detects a multipart request, it will resolve it via the configured MultipartResolver and pass on a wrapped HttpServletRequest. Controllers can then cast their given request to the MultipartHttpServletRequest interface, which allows for access to any MultipartFiles. Note that this cast is only supported in case of an actual multipart request.

 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
   MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
   MultipartFile multipartFile = multipartRequest.getFile("image");
   ...
 }

Instead of direct access, command or form controllers can register a ByteArrayMultipartFileEditor or StringMultipartFileEditor with their data binder, to automatically apply multipart content to form bean properties.

As an alternative to using a MultipartResolver with a DispatcherServlet, a MultipartFilter can be registered in web.xml. It will delegate to a corresponding MultipartResolver bean in the root application context. This is mainly intended for applications that do not use Spring's own web MVC framework.

Note: There is hardly ever a need to access the MultipartResolver itself from application code. It will simply do its work behind the scenes, making MultipartHttpServletRequests available to controllers.

关于MultipartResolver的接口文档,请参考Spring5.2.x的官方文档《https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/MultipartResolver.html

文件上传策略接口MultipartResolver的实现

public interface MultipartResolver {
    // 判断request是否为文件上传请求
    boolean isMultipart(HttpServletRequest request);

    // 将HttpServletRequest请求转化为MultipartHttpServletRequest
    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

    // 清空:入参是MultipartHttpServletRequest
    void cleanupMultipart(MultipartHttpServletRequest request);
}

当用户的请求到DispatcherServlet时,

1)DispatcherServlet会先在webapplicationContext.xml中找一个名为“multipartResolver”的bean.

2)如果有,则调用MultipartResolver的第一个方法(isMultipart(...)),该方法会返回这个请求是否是通过enctype=”multipart/form-data”方式提交的,如果是,则调用第二个方法(resolveMultipart(...))的,这个方法会把当前的HttpServletRequest换成MultipartHttpServletRequest,并传给后面的Controller处理

3)如果没有这个bean(也可以修改DispatcherServlet的 MULTIPART_RESOLVER_BEAN_NAME属性来修改查找的名字),或者第一个方法没有返回true,则不做处理,继续传递HttpServletRequest。

MultipartResolver是一个为多文件上传提供了解决方案的策略接口,将普通的HttpServletRequest封装成MultipartHttpServletRequest,在Spring中常见的两个实现方式,分别是:

1)StandardServletMultipartResolver:使用Servlet3.0标准上传方式,将HttpServletRequest转化为StandardServletMultipartResolver,以tomcat为例,从 Tomcat 7.0.x的版本开始就支持 Servlet 3.0了。

2)CommonsMultipartResolver:使用apache的common-fileupload,将HttpServletRequest转化为DefaultMultipartHttpServletRequest(需要依赖common-fileupload.jar,common-io.jar)。

在SpringMVC中MultipartResolver组件没有提供默认值,实际上如果上传文件不使用MultipartResovler组件封装成MultipartHttpServletRequest,直接用原生HttpServletRequest request也是可以的。

StandardServletMultipartResolver类

StandardServletMultipartResolver实现了MultipartResolver接口,resolveMultipart(HttpServletRequest request)方法中resolveLazily是判断是否要延迟解析文件(通过XML可以设置)

package org.springframework.web.multipart.support;

public class StandardServletMultipartResolver implements MultipartResolver {
    // 是否立即解析
    private boolean resolveLazily = false;

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

    // 是否上传文件
    @Override
    public boolean isMultipart(HttpServletRequest request) {
        return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
    }
    // 将HttpServletRequest解析为MultipartHttpServlet(StandardMultipartHttpServletRequest)
    @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 {
                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);
            }
        }
    }

}

1)resolveMultipart(HttpServletRequest request)方法:是对请求数据进行解析,并返回解析后的包装类StandardMultipartHttpServletRequest对象,其中对request请求数据解析是在StandardMultipartHttpServletRequest的parseRequest(request)方法中执行:

    public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
            throws MultipartException {

        super(request);
// 如果不是懒解析,则立即解析
if (!lazyParsing) { parseRequest(request); } } // 对request请求数据进行解析 private void parseRequest(HttpServletRequest request) { try { Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size()); for (Part part : parts) { String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION); ContentDisposition disposition = ContentDisposition.parse(headerValue); String filename = disposition.getFilename(); if (filename != null) { if (filename.startsWith("=?") && filename.endsWith("?=")) { filename = MimeDelegate.decode(filename); } files.add(part.getName(), new StandardMultipartFile(part, filename)); } else { this.multipartParameterNames.add(part.getName()); } } setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } }

2)StandardMultipartHttpServletRequest#parseRequest(HttpServletRequest request) 方法利用了 servlet3.0 的 request.getParts() 方法获取上传文件,并将其封装到 MultipartFile 对象中。

3)该组件的一些配置信息可以在web.xml的<servlet>标签中配置:

        <!--  StandardServletMultipartResolver 属性配置  -->
        <multipart-config>
            <!--上传到/tmp/upload 目录-->
            <location>/tmp/upload</location>
            <!--文件大小为2M-->
            <max-file-size>2097152</max-file-size>
            <!--整个请求不超过4M-->
            <max-request-size>4194304</max-request-size>
            <!--所有文件都要写入磁盘-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

CommonsMultipartResolver类

CommonsMultipartResolver实现了MultipartResolver接口,resolveMultipart(HttpServletRequest request)方法中resolveLazily是判断是否要延迟解析文件(通过XML可以设置)

package org.springframework.web.multipart.commons;

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

    //
    public CommonsMultipartResolver() {
        super();
    }

    // 构造函数
    public CommonsMultipartResolver(ServletContext servletContext) {
        this();
        setServletContext(servletContext);
    }

    // 设置是否懒解析
    public void setResolveLazily(boolean resolveLazily) {
        this.resolveLazily = resolveLazily;
    }

    // 使用common-fileupload.jar进行文件
    @Override
    protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
        return new ServletFileUpload(fileItemFactory);
    }
        // 设置ServletContext
    @Override
    public void setServletContext(ServletContext servletContext) {
        if (!isUploadTempDirSpecified()) {
            getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext));
        }
    }

        // 是否上传文件
    @Override
    public boolean isMultipart(HttpServletRequest request) {
        return ServletFileUpload.isMultipartContent(request);
    }
        // 解析上传文件请求
    @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());
        }
    }

    // 解析请求
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);
        try {
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
            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);
        }
    }

    // 文件编码格式,可以通过xml设置
    protected String determineEncoding(HttpServletRequest request) {
        String encoding = request.getCharacterEncoding();
        if (encoding == null) {
            encoding = getDefaultEncoding();
        }
        return encoding;
    }
        // 清理
    @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);
            }
        }
    }

}

1)resolveMultipart(HttpServletRequest request)方法是对请求数据进行解析工作的方法:

1.1)当resolveLazily为false时,会立即调用parseRequest(HttpServletRequest request)方法,对请求数据进行解析,然后将解析结果封装到DefaultMultipartHttpServletRequest中;

1.2)当resolveLazily为true时,会在DefaultMultipartHttpServletRequest的initializeMultipart()方法调用parseRequest()方法对请求数据进行解析,而initializeMultipart()方法有时被getMultipartFiles()方法调用,即当需要获取文件信息时,才会去解析请求数据,这种方式采用了懒加载的思想。

2)在CommonsMultipartResolver#parseRequest()方法中,首先调用了prepareFileUpload()方法来根据编码类型确定一个FIleUpload实例,然后利用这个FileUpload实例解析请求数据后得到文件信息,然后将文件信息解析成CommonsMultipartFile(实现了MultipartFile接口)并包装到MultipartParsingResut对象中。

    /**
     * Parse the given servlet request, resolving its multipart elements.
     * @param request the request to parse
     * @return the parsing result
     * @throws MultipartException if multipart resolution failed.
     */
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);
        try {
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
            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);
        }
    }

3)resovleLazily属性可以通过xml配置;

4)determinedEncoding(HttpServletRequest request)方法中的=defaultEncoding可以通过xml配置。

5)另外maxUploadSize属性也可以通过xml配置。

文件上传请求接口MultipartRequest的实现

文件上传处理过程中国使用的是MultipartHttpRequestServlet,它继承自接口MultipartRequest。

SpringMvc提供了两种对MultipartRequest接口的实现类:StandardMultipartHttpServletRequestDefaultMultipartHttpServletRequest,它们都继承自AbstractMultipartHttpServletRequest,MultipartHttpServletRequest接口定义的方法基本都在AbstractMultipartHttpServletRequest中实现。

MultipartRequest接口:

package org.springframework.web.multipart;

public interface MultipartRequest {
// 返回表单参数名称(而文件原名)列表 Iterator
<String> getFileNames(); // 根据表单文件参数名称返回MultipartFile对象 @Nullable MultipartFile getFile(String name); // 根据表单参数文件名称返回MultipartFile列表对象 List<MultipartFile> getFiles(String name); // 获取{文件参数名称:MultipartFile对象}集合 Map<String, MultipartFile> getFileMap(); // 获取{文件参数名称:MultipartFile对象}集合 MultiValueMap<String, MultipartFile> getMultiFileMap(); // 根据参数或文件名称获取内容类型,不存在时,返回null @Nullable String getMultipartContentType(String paramOrFileName); }

MultipartHttpServleRequest接口:

package org.springframework.web.multipart;

public interface MultipartHttpServletRequest extends HttpServletRequest, MultipartRequest {
    // 返回请参数类型实例
    @Nullable
    HttpMethod getRequestMethod();
    // 返回请求的HttpHeaders实例
    HttpHeaders getRequestHeaders();
    // 返回包含contentType的HttpHeaders实例
    @Nullable
    HttpHeaders getMultipartHeaders(String paramOrFileName);
}

AbstractMultipartHttpServletRequest接口:

package org.springframework.web.multipart.support;

public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper
        implements MultipartHttpServletRequest {
    //注意: MultiValueMap 等价于 Map<String ,List<MultipartFile>>
    @Nullable
    private MultiValueMap<String, MultipartFile> multipartFiles;

    // 包装给定的 HttpServletRequest 为MultipartHttpServletRequest
    protected AbstractMultipartHttpServletRequest(HttpServletRequest request) {
        super(request);
    }
    // 返回前原始HttpServletRequest对象
    @Override
    public HttpServletRequest getRequest() {
        return (HttpServletRequest) super.getRequest();
    }
    // 返回请求类型实例
    @Override
    public HttpMethod getRequestMethod() {
        return HttpMethod.resolve(getRequest().getMethod());
    }
    // 返回请求中header中所有信息
    @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;
    }
    // 上传时:file文件对应form表单的key(不是file自身的属性name)
    @Override
    public Iterator<String> getFileNames() {
        return getMultipartFiles().keySet().iterator();
    }
    // 由于可能上传多个文件, 这里范湖first
    @Override
    public MultipartFile getFile(String name) {
        return getMultipartFiles().getFirst(name);
    }
    // 根据form表单key, 获取文件列表
    @Override
    public List<MultipartFile> getFiles(String name) {
        List<MultipartFile> multipartFiles = getMultipartFiles().get(name);
        if (multipartFiles != null) {
            return multipartFiles;
        }
        else {
            return Collections.emptyList();
        }
    }
    // 上传时,返回key Vs MultipartFile对象
    @Override
    public Map<String, MultipartFile> getFileMap() {
        return getMultipartFiles().toSingleValueMap();
    }
    // 上传时,返回key Vs MultipartFile
    @Override
    public MultiValueMap<String, MultipartFile> getMultiFileMap() {
        return getMultipartFiles();
    }

    // 是否懒处理
    public boolean isResolved() {
        return (this.multipartFiles != null);
    }

    // 初始化时,set该属性
    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");
    }
}

DefaultMultipartHttpServletRequest

package org.springframework.web.multipart.support;

import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;

/**
 * Default implementation of the
 * {@link org.springframework.web.multipart.MultipartHttpServletRequest}
 * interface. Provides management of pre-generated parameter values.
 *
 * <p>Used by {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
 *
 * @author Trevor D. Cook
 * @author Juergen Hoeller
 * @author Arjen Poutsma
 * @since 29.09.2003
 * @see org.springframework.web.multipart.MultipartResolver
 */
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;


    /**
     * Wrap the given HttpServletRequest in a MultipartHttpServletRequest.
     * @param request the servlet request to wrap
     * @param mpFiles a map of the multipart files
     * @param mpParams a map of the parameters to expose,
     * with Strings as keys and String arrays as values
     */
    public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles,
            Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {

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

    /**
     * Wrap the given HttpServletRequest in a MultipartHttpServletRequest.
     * @param request the servlet request to wrap
     */
    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;
        }
    }


    /**
     * Set a Map with parameter names as keys and String array objects as values.
     * To be invoked by subclasses on initialization.
     */
    protected final void setMultipartParameters(Map<String, String[]> multipartParameters) {
        this.multipartParameters = multipartParameters;
    }

    /**
     * Obtain the multipart parameter Map for retrieval,
     * lazily initializing it if necessary.
     * @see #initializeMultipart()
     */
    protected Map<String, String[]> getMultipartParameters() {
        if (this.multipartParameters == null) {
            initializeMultipart();
        }
        return this.multipartParameters;
    }

    /**
     * Set a Map with parameter names as keys and content type Strings as values.
     * To be invoked by subclasses on initialization.
     */
    protected final void setMultipartParameterContentTypes(Map<String, String> multipartParameterContentTypes) {
        this.multipartParameterContentTypes = multipartParameterContentTypes;
    }

    /**
     * Obtain the multipart parameter content type Map for retrieval,
     * lazily initializing it if necessary.
     * @see #initializeMultipart()
     */
    protected Map<String, String> getMultipartParameterContentTypes() {
        if (this.multipartParameterContentTypes == null) {
            initializeMultipart();
        }
        return this.multipartParameterContentTypes;
    }

}
View Code

该类包含了几个重要属性:

1)集成基类AbstractMultipartHttpServletRequest的multipartFiles属性:用来存储上传文件的集合;

2)multipartParameterNames属性:用来存储表单中key,value的集合;

3)multipartParameterContentTypes属性:表单key,contentType的集合;

4)request属性:来自MultipartHttpServletRequest基类HttpServletRequest属性request。

StandardMultipartHttpServletRequst

package org.springframework.web.multipart.support;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.mail.internet.MimeUtility;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;

/**
 * Spring MultipartHttpServletRequest adapter, wrapping a Servlet 3.0 HttpServletRequest
 * and its Part objects. Parameters get exposed through the native request's getParameter
 * methods - without any custom processing on our side.
 *
 * @author Juergen Hoeller
 * @author Rossen Stoyanchev
 * @since 3.1
 * @see StandardServletMultipartResolver
 */
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {

    @Nullable
    private Set<String> multipartParameterNames;


    /**
     * Create a new StandardMultipartHttpServletRequest wrapper for the given request,
     * immediately parsing the multipart content.
     * @param request the servlet request to wrap
     * @throws MultipartException if parsing failed
     */
    public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException {
        this(request, false);
    }

    /**
     * Create a new StandardMultipartHttpServletRequest wrapper for the given request.
     * @param request the servlet request to wrap
     * @param lazyParsing whether multipart parsing should be triggered lazily on
     * first access of multipart files or parameters
     * @throws MultipartException if an immediate parsing attempt failed
     * @since 3.2.9
     */
    public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
            throws MultipartException {

        super(request);
        if (!lazyParsing) {
            parseRequest(request);
        }
    }


    private void parseRequest(HttpServletRequest request) {
        try {
            Collection<Part> parts = request.getParts();
            this.multipartParameterNames = new LinkedHashSet<>(parts.size());
            MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
            for (Part part : parts) {
                String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
                ContentDisposition disposition = ContentDisposition.parse(headerValue);
                String filename = disposition.getFilename();
                if (filename != null) {
                    if (filename.startsWith("=?") && filename.endsWith("?=")) {
                        filename = MimeDelegate.decode(filename);
                    }
                    files.add(part.getName(), new StandardMultipartFile(part, filename));
                }
                else {
                    this.multipartParameterNames.add(part.getName());
                }
            }
            setMultipartFiles(files);
        }
        catch (Throwable ex) {
            handleParseFailure(ex);
        }
    }

    protected void handleParseFailure(Throwable ex) {
        String msg = ex.getMessage();
        if (msg != null && msg.contains("size") && msg.contains("exceed")) {
            throw new MaxUploadSizeExceededException(-1, ex);
        }
        throw new MultipartException("Failed to parse multipart servlet request", ex);
    }

    @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;
    }

    @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);
        }
    }


    /**
     * Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object.
     */
    @SuppressWarnings("serial")
    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));
        }
    }


    /**
     * Inner class to avoid a hard dependency on the JavaMail API.
     */
    private static class MimeDelegate {

        public static String decode(String value) {
            try {
                return MimeUtility.decodeText(value);
            }
            catch (UnsupportedEncodingException ex) {
                throw new IllegalStateException(ex);
            }
        }
    }

}
View Code

该类包含了几个重要属性:

1)集成基类AbstractMultipartHttpServletRequest的multipartFiles属性:用来存储上传文件的集合;

2)multipartParameterNames属性:用来存储表单中key,value的集合;

3)request属性:来自MultipartHttpServletRequest基类HttpServletRequest属性request。

ResolveMultipart组件上传文件的用法

在SpringMvc中默认并未给ResolveMultipart实现,默认为null,此时文件上传使用HttpServletRequest携带上传文件到服务端。另外就是可以通过ResovlerMultipart组件实现文件上传。ResolverMultipart组件实现上传包含两种实现:StandardServletMultipartResolver/CommonsMultipartResolver。

需要注意事项:

1)不配置ResovleMultipart方案(采用HttpServletRequest进行传递提交数据),后端接口中请求对象必须是HttpServletRequest;

2)配置ResolveMultipart为StandardServletMultipartResolver方案,后端接口中定义请求对象可以使MultipartFile、MultipartFile[]、HttpServletRequest、MultipartHttpServletRequest、StandardServletMultipartResolver。但是几种用法有区别:

2.1)MultipartFile是用来接收单个文件上传使用;多个<input type="file" ...>时,接收第一个<input type="file" ...>文件;

比如:

2.1.1)多标签

<input type="file" name="file1"/>
<input type="file" name="file2"/>

此时,接收第一个标签:name="file1"的上传文件。

2.1.2)一组标签

上传文件1:<input type="file" id="file1" name="file1"/>
上传文件2:<input type="file" id="file2" name="file1"/>
上传文件3:<input type="file" id="file3" name="file2"/>

此时只接收第一组标签的第一个标签:<input type="file" id="file1" name="file1" />的这一个上传文件。

2.2)MultipartFile[]接收第一组标签

2.2.1)多标签

<input type="file" name="file1"/>
<input type="file" name="file2"/>

此时,接收第一个标签:name="file1"的上传文件。

2.1.2)一组标签

上传文件1:<input type="file" id="file1" name="file1"/>
上传文件2:<input type="file" id="file2" name="file1"/>
上传文件3:<input type="file" id="file3" name="file2"/>

此时只接收第一组标签:<input type="file" id="file1" name="file1" />和<input type="file" id="file2" name="file1" />的这一个上传文件。

2.3)HttpServletRequest、MultipartHttpServletRequest、StandardServletMultipartResolver接收

其实上边这几种都是StandardServletMultipartResolver类型。

3)配置ResolveMultipart为CommonsMultipartResolver方案,后端接口中定义请求对象可以使MultipartFile、MultipartFile[]、HttpServletRequest、MultipartHttpServletRequest、CommonsMultipartResolver。具体区别与上边‘StandardServletMultipartResolver方案’大致一致。

下边就对这几种实现上传文件的方案进行讲解如何使用。

使用HttpServletRequest实现(不配置ResolveMultipart组件)

Post方式:

/WEB-INF/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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--扫描所有的 spring包下的文件;-->
    <!--当然需要在spring配置文件里面配置一下自动扫描范围
    <context:component-scan base-package="*"/>
    *代表你想要扫描的那些包的目录所在位置。Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有的.class文件,
    所有标注了 @Repository 的类都将被注册为 Spring Bean。
    -->
    <context:component-scan base-package="com.dx.test"/>
    <!--新增加的两个配置,这个是解决406问题的关键-->
    <!--mvc注解驱动(可代替注解适配器与注解映射器的配置),默认加载很多参数绑定方法(实际开发时使用)-->
    <context:annotation-config/>
    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    </bean>

    <!--自己后加的,该BeanPostProcessor将自动对标注@Autowired的bean进行注入-->
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean>
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <property name="messageConverters">
            <list>
                <!--<ref bean="stringHttpMessageConverter"/>-->
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
                <!--
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                -->
            </list>
        </property>
    </bean>

</beans>

web.xml

<!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>
    <display-name>springmvcdemo</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--结束后端数据输出到前端乱码问题-->
    <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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--可以通过配置覆盖默认'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>myAppServletName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <!--  StandardServletMultipartResolver 属性配置  -->
        <multipart-config>
            <!--上传到/tmp/upload 目录,如果配置为/使用HttpServletRequest上传时,可能会抛出异常/无权限操作-->
            <!--<location>/</location>-->
            <!--文件大小为2M-->
            <max-file-size>2097152</max-file-size>
            <!--整个请求不超过4M-->
            <max-request-size>4194304</max-request-size>
            <!--所有文件都要写入磁盘-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

    </servlet>
    <servlet-mapping>
        <servlet-name>myAppServletName</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

此时上传使用的是HttpServletRequest,因此需要:

1)在web.xml的<servlet>下配置<multipart-config>配置项;

2)在pom.xml引入servlet依赖。

        <!-- servlet相关
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
         -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>

测试表单:

    <h2>Post包含上传文件提交:</h2>
    <form:form name="article" method="POST" action="update_with_post_file" modelAttribute="article" enctype="multipart/form-data">
       Id:<form:hidden path="id"/>
      Title: <form:input path="title" style="width:200px;"/><br/>
      Content: <form:input path="content" style="width:200px;"/><br/>
       yourfile: <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       yourfile2:
       <input type="file" name="execelFile"/><br/>
      <input type="submit" value="Submit" />
    </form:form>

后台接口:

    @RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article, HttpServletRequest request) throws IOException, ServletException {
        System.out.println(article);
        Collection<Part> parts = request.getParts();
        for (Part part : parts) {
            System.out.println(part.getName() + "->"+part.getContentType());
        }

        String id = request.getParameter("id");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        System.out.println(String.format("%s,%s,%s", id, title, content));

        return "index";
    }

断点查看parts变量属性如下:

后台打印信息如下:

[DEBUG] POST "/article/update_with_post_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_post_file(ArticleModel, HttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}
id->null
title->null
content->null
files->application/octet-stream
files->application/octet-stream
files->image/svg+xml
execelFile->application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1,算法与数据结构--综合提升篇(c++版),文章内容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK

Put方式:

此时,只需要基于上边的实现方案稍作调整即可:

1)put表单:

    <h2>Put包含上传文件提交:</h2>
    <form method="POST" name="article" action="update_with_put_file" enctype="multipart/form-data">
        <input type="hidden" name="_method" value="PUT"/>
        Id:<input name="id" id="id" value="${article.id}"/><br/>
        Title:<input name="title" id="title" value="${article.title}"/><br/>
        Content:<input name="content" id="content" value="${article.content}"/><br/>
       yourfile: <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       yourfile2:
       <input type="file" name="execelFile"/><br/>
        <input type="submit" value="Submit" />
    </form>

2)web.xml引入hiddenHttpFilter,applicationContext.xml不做任何调整:

<!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>
    <display-name>springmvcdemo</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--
    全局初始化数据,spring的监听器读取此配置文件,多个配置文件用分号分隔
    如果在web.xml中不写任何参数配置信息,默认的路径是/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml;
    如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
    -->
    <!--
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    -->


    <!-- 文件上传与下载过滤器:form表单中存在文件时,该过滤器可以处理http请求中的文件,被该过滤器过滤后会用post方法提交,form表单需设为enctype="multipart/form-data"-->
    <!-- 注意:必须放在HiddenHttpMethodFilter过滤器之前 -->
    <!--spring中配置的id为multipartResolver的解析器-->
    <!--
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>-->
        <!--<url-pattern>/*</url-pattern>-->
        <!--<servlet-name>myAppServletName</servlet-name>-->
        <!--<url-pattern>/*</url-pattern>
    </filter-mapping>-->
    <!--
    注意:HiddenHttpMethodFilter必须作用于dispatcher前
    请求method支持 put 和 delete 必须添加该过滤器
    作用:可以过滤所有请求,并可以分为四种
    使用该过滤器需要在前端页面加隐藏表单域
    <input type="hidden" name="_method" value="请求方式(put/delete)">
    post会寻找_method中的请求式是不是put 或者 delete,如果不是 则默认post请求
    -->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <servlet-name>myAppServletName</servlet-name>
    </filter-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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--可以通过配置覆盖默认'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>myAppServletName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <!--  StandardServletMultipartResolver 属性配置  -->
        <multipart-config>
            <!--上传到/tmp/upload 目录,如果配置为/使用HttpServletRequest上传时,可能会抛出异常/无权限操作-->
            <!--<location>/</location>-->
            <!--文件大小为2M-->
            <max-file-size>2097152</max-file-size>
            <!--整个请求不超过4M-->
            <max-request-size>4194304</max-request-size>
            <!--所有文件都要写入磁盘-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

    </servlet>
    <servlet-mapping>
        <servlet-name>myAppServletName</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

3)后端接口实现:

    @RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, HttpServletRequest request) throws IOException, ServletException {
        System.out.println(article);
        Collection<Part> parts=request.getParts();
        for(Part part:parts){
            System.out.println(part.getName()+"->"+part.getContentType());
        }

        String id = request.getParameter("id");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        System.out.println(String.format("%s,%s,%s", id, title, content));

        return "index";
    }

此时测试后端打印信息如下:

[DEBUG] PUT "/article/update_with_put_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_put_file(ArticleModel, HttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}
_method->null
id->null
title->null
content->null
files->application/octet-stream
files->application/octet-stream
files->image/svg+xml
execelFile->application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1,算法与数据结构--综合提升篇(c++版),文章内容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK

使用StandardServletMultipartResolver实现

注意:此时在web.xml中用不用MultipartFilter都行,使用了也能正常运行: 

    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>
        <servlet-name>myAppServletName</servlet-name>
    </filter-mapping>

1)在web.xml中不使用MultipartFilter时,DispatcherServlet会直接读取web.xml中<servlet><init-param>下的applicationContext.xml中multipartResolver Bean;

2)在web.xml中  使用MultipartFilter时,会先走Tomcat doFilter->执行org.springframework.web.filter.OncePerRequestFilter.doFilter()

之后会执行MultipartFilter#doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        MultipartResolver multipartResolver = lookupMultipartResolver(request);

        HttpServletRequest processedRequest = request;
        if (multipartResolver.isMultipart(processedRequest)) {
            if (logger.isTraceEnabled()) {
                logger.trace("Resolving multipart request");
            }
            //如果当前上传文件,则使用multipartResolver#resolveMultipart(request)对request进行解析:
            //1)如果multipartResovler是StandardServletMultipartResolver,则执行函数会将request解析为:StandardMultipartHttpServletRequest
            //2)如果multipartResovler是CommonsMultipartResolver,则执行函数会将request解析为:DefaultMultipartHttpServletRequest
            processedRequest = multipartResolver.resolveMultipart(processedRequest);
        }
        else {
            // A regular request...
            if (logger.isTraceEnabled()) {
                logger.trace("Not a multipart request");
            }
        }

        try {
            filterChain.doFilter(processedRequest, response);
        }
        finally {
            if (processedRequest instanceof MultipartHttpServletRequest) {
                multipartResolver.cleanupMultipart((MultipartHttpServletRequest) processedRequest);
            }
        }
    }

Post方式:

1)web.xml需要配置在<servlet>下配置上传配置:

<!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>
    <display-name>springmvcdemo</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--
    全局初始化数据,spring的监听器读取此配置文件,多个配置文件用分号分隔
    如果在web.xml中不写任何参数配置信息,默认的路径是/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml;
    如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
    -->
    <!--
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    -->


    <!-- 文件上传与下载过滤器:form表单中存在文件时,该过滤器可以处理http请求中的文件,被该过滤器过滤后会用post方法提交,form表单需设为enctype="multipart/form-data"-->
    <!-- 注意:必须放在HiddenHttpMethodFilter过滤器之前 -->
    <!--spring中配置的id为multipartResolver的解析器-->
    <!--
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>-->
        <!--<url-pattern>/*</url-pattern>-->
        <!--<servlet-name>myAppServletName</servlet-name>-->
        <!--<url-pattern>/*</url-pattern>
    </filter-mapping>-->
    <!--
    注意:HiddenHttpMethodFilter必须作用于dispatcher前
    请求method支持 put 和 delete 必须添加该过滤器
    作用:可以过滤所有请求,并可以分为四种
    使用该过滤器需要在前端页面加隐藏表单域
    <input type="hidden" name="_method" value="请求方式(put/delete)">
    post会寻找_method中的请求式是不是put 或者 delete,如果不是 则默认post请求
    -->
    <!--
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <servlet-name>myAppServletName</servlet-name>
    </filter-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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--可以通过配置覆盖默认'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>myAppServletName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <!--  StandardServletMultipartResolver 属性配置  -->
        <multipart-config>
            <!--上传到/tmp/upload 目录,如果配置为/使用HttpServletRequest上传时,可能会抛出异常/无权限操作-->
            <!--<location>/</location>-->
            <!--文件大小为2M-->
            <max-file-size>2097152</max-file-size>
            <!--整个请求不超过4M-->
            <max-request-size>4194304</max-request-size>
            <!--所有文件都要写入磁盘-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

    </servlet>
    <servlet-mapping>
        <servlet-name>myAppServletName</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

2)applicationContext.xml中要配置resoveMultipart为StandardServletMultipartResolver

<?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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--扫描所有的 spring包下的文件;-->
    <!--当然需要在spring配置文件里面配置一下自动扫描范围
    <context:component-scan base-package="*"/>
    *代表你想要扫描的那些包的目录所在位置。Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有的.class文件,
    所有标注了 @Repository 的类都将被注册为 Spring Bean。
    -->
    <context:component-scan base-package="com.dx.test"/>
    <!--新增加的两个配置,这个是解决406问题的关键-->
    <!--mvc注解驱动(可代替注解适配器与注解映射器的配置),默认加载很多参数绑定方法(实际开发时使用)-->
    <context:annotation-config/>
    <mvc:annotation-driven/>
    <!--
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.dx.test.interceptors.LogInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    -->
    <!--end-->

    <!--
        <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
        <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
    -->

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    </bean>

    <!-- 配置文件上传解析器 enctype="multipart/form-data" -->
    <!--<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">-->
        <!-- 设定默认编码 -->
        <!--<property name="defaultEncoding" value="UTF-8" />-->
        <!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
        <!--<property name="maxUploadSize" value="5242880" />-->
        <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240,40*1024 -->
        <!--<property name="maxInMemorySize" value="40960" />-->
        <!-- 上传文件的临时路径 fileUpload/temp-->
        <!--<property name="uploadTempDir" value="/" />-->
        <!-- 延迟文件解析 -->
        <!--<property name="resolveLazily" value="true"/>-->
    <!--</bean>-->
    <!--
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8" />
        <property name="maxUploadSize" value="5242880" />
        <property name="maxInMemorySize" value="40960" />
        <property name="uploadTempDir" value="fileUpload/temp" />
        <property name="resolveLazily" value="true"/>
    </bean>
    -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />

    <!--自己后加的,该BeanPostProcessor将自动对标注@Autowired的bean进行注入-->
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean>
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <property name="messageConverters">
            <list>
                <!--<ref bean="stringHttpMessageConverter"/>-->
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
                <!--
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                -->
            </list>
        </property>
    </bean>

</beans>

3)提交页面为:

<h2>Post包含上传文件提交:</h2>
    <form:form name="article" method="POST" action="update_with_post_file" modelAttribute="article" enctype="multipart/form-data">
       Id:<form:hidden path="id"/>
      Title: <form:input path="title" style="width:200px;"/><br/>
      Content: <form:input path="content" style="width:200px;"/><br/>
       yourfile: <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       yourfile2:
       <input type="file" name="execelFile"/><br/>
      <input type="submit" value="Submit" />
    </form:form>

4)后端接口

    @RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article,/*HttpServletRequest request*//*MultipartHttpServletRequest request*/StandardMultipartHttpServletRequest request) throws IOException, ServletException {
        System.out.println(article);

        MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap();
        System.out.println("「");
        for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) {
            StringBuilder builder = new StringBuilder();

            part.getValue().forEach(s -> {
                builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],");
            });
            builder.delete(builder.length() - 1, builder.length());
            System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}");
        }
        System.out.println("」");

        String id = request.getParameter("id");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        System.out.println(String.format("%s,%s,%s", id, title, content));

        return "index";
    }

提交打印日志:

[DEBUG] POST "/article/update_with_post_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_post_file(ArticleModel, StandardMultipartHttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}
「
files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,test.svg,test.svg]}
execelFile->{execelFile,[execelFile,数据接口.xlsx,数据接口.xlsx]}
」
1,算法与数据结构--综合提升篇(c++版),文章内容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK

Put方式:

基于post方式,put方式只需要修改三处:

1)applicationContext.xml不需要修改,web.xml引入hiddenHttpMethodFilter

<!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>
    <display-name>springmvcdemo</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--
    全局初始化数据,spring的监听器读取此配置文件,多个配置文件用分号分隔
    如果在web.xml中不写任何参数配置信息,默认的路径是/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml;
    如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
    -->
    <!--
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    -->


    <!-- 文件上传与下载过滤器:form表单中存在文件时,该过滤器可以处理http请求中的文件,被该过滤器过滤后会用post方法提交,form表单需设为enctype="multipart/form-data"-->
    <!-- 注意:必须放在HiddenHttpMethodFilter过滤器之前 -->
    <!--spring中配置的id为multipartResolver的解析器-->
    <!--
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>-->
        <!--<url-pattern>/*</url-pattern>-->
        <!--<servlet-name>myAppServletName</servlet-name>-->
        <!--<url-pattern>/*</url-pattern>
    </filter-mapping>-->
    <!--
    注意:HiddenHttpMethodFilter必须作用于dispatcher前
    请求method支持 put 和 delete 必须添加该过滤器
    作用:可以过滤所有请求,并可以分为四种
    使用该过滤器需要在前端页面加隐藏表单域
    <input type="hidden" name="_method" value="请求方式(put/delete)">
    post会寻找_method中的请求式是不是put 或者 delete,如果不是 则默认post请求
    -->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <servlet-name>myAppServletName</servlet-name>
    </filter-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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--可以通过配置覆盖默认'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>myAppServletName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <!--  StandardServletMultipartResolver 属性配置  -->
        <multipart-config>
            <!--上传到/tmp/upload 目录,如果配置为/使用HttpServletRequest上传时,可能会抛出异常/无权限操作-->
            <!--<location>/</location>-->
            <!--文件大小为2M-->
            <max-file-size>2097152</max-file-size>
            <!--整个请求不超过4M-->
            <max-request-size>4194304</max-request-size>
            <!--所有文件都要写入磁盘-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

    </servlet>
    <servlet-mapping>
        <servlet-name>myAppServletName</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

2)提交页面中引入<input type="hidden" name="_method" value="put"/> 

    <h2>Put包含上传文件提交:</h2>
    <form method="POST" name="article" action="update_with_put_file" enctype="multipart/form-data">
        <input type="hidden" name="_method" value="PUT"/>
        Id:<input name="id" id="id" value="${article.id}"/><br/>
        Title:<input name="title" id="title" value="${article.title}"/><br/>
        Content:<input name="content" id="content" value="${article.content}"/><br/>
       yourfile: <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       yourfile2:
       <input type="file" name="execelFile"/><br/>
        <input type="submit" value="Submit" />
    </form>

3)后端接口需要以put方式接收

    @RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, /*HttpServletRequest request*//*MultipartHttpServletRequest request*/StandardMultipartHttpServletRequest request) throws IOException, ServletException {
        System.out.println(article);

        MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap();
        System.out.println("「");
        for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) {
            StringBuilder builder = new StringBuilder();

            part.getValue().forEach(s -> {
                builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],");
            });
            builder.delete(builder.length() - 1, builder.length());
            System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}");
        }
        System.out.println("」");

        String id = request.getParameter("id");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        System.out.println(String.format("%s,%s,%s", id, title, content));

        return "index";
    }

此事后台打印信息:

[DEBUG] PUT "/article/update_with_put_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_put_file(ArticleModel, StandardMultipartHttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}
「
files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,SpringBoot-Converter,SpringBoot-Converter]}
execelFile->{execelFile,[execelFile,数据接口.xlsx,数据接口.xlsx]}
」
1,算法与数据结构--综合提升篇(c++版),文章内容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK

使用CommonsMultipartResolver实现

因该方案内部采用common-uploadfile,因此需要在pom.xml中引入依赖:

        <!--form 设置为enctype="multipart/form-data",多文件上传,在applicationContext.xml中配置了bean Commons multipartResolver时,需要依赖该包。-->
        <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.5</version>
        </dependency>

Post方式:

1)/WEB-INF/applicationContext.xml需要引入resolveMultipart为:CommonsMultipartResolver

<?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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--扫描所有的 spring包下的文件;-->
    <!--当然需要在spring配置文件里面配置一下自动扫描范围
    <context:component-scan base-package="*"/>
    *代表你想要扫描的那些包的目录所在位置。Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有的.class文件,
    所有标注了 @Repository 的类都将被注册为 Spring Bean。
    -->
    <context:component-scan base-package="com.dx.test"/>
    <!--新增加的两个配置,这个是解决406问题的关键-->
    <!--mvc注解驱动(可代替注解适配器与注解映射器的配置),默认加载很多参数绑定方法(实际开发时使用)-->
    <context:annotation-config/>
    <mvc:annotation-driven/>
    <!--
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.dx.test.interceptors.LogInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    -->
    <!--end-->

    <!--
        <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
        <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
    -->

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    </bean>

    <!-- 配置文件上传解析器 enctype="multipart/form-data" -->
    <!--<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">-->
        <!-- 设定默认编码 -->
        <!--<property name="defaultEncoding" value="UTF-8" />-->
        <!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
        <!--<property name="maxUploadSize" value="5242880" />-->
        <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240,40*1024 -->
        <!--<property name="maxInMemorySize" value="40960" />-->
        <!-- 上传文件的临时路径 fileUpload/temp-->
        <!--<property name="uploadTempDir" value="/" />-->
        <!-- 延迟文件解析 -->
        <!--<property name="resolveLazily" value="true"/>-->
    <!--</bean>-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8" />
        <property name="maxUploadSize" value="5242880" />
        <property name="maxInMemorySize" value="40960" />
        <property name="uploadTempDir" value="fileUpload/temp" />
        <property name="resolveLazily" value="true"/>
    </bean>
    <!--
    <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />
    -->
    <!--自己后加的,该BeanPostProcessor将自动对标注@Autowired的bean进行注入-->
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean>
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <property name="messageConverters">
            <list>
                <!--<ref bean="stringHttpMessageConverter"/>-->
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
                <!--
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                -->
            </list>
        </property>
    </bean>

</beans>

2)/WEB-INF/web.xml引入ContextLoaderListener,和MultipartFilter

<!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>
    <display-name>springmvcdemo</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--
    全局初始化数据,spring的监听器读取此配置文件,多个配置文件用分号分隔
    如果在web.xml中不写任何参数配置信息,默认的路径是/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml;
    如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
    -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 文件上传与下载过滤器:form表单中存在文件时,该过滤器可以处理http请求中的文件,被该过滤器过滤后会用post方法提交,form表单需设为enctype="multipart/form-data"-->
    <!-- 注意:必须放在HiddenHttpMethodFilter过滤器之前 -->
    <!--spring中配置的id为multipartResolver的解析器-->
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>-->
        <!--<url-pattern>/*</url-pattern>-->
        <servlet-name>myAppServletName</servlet-name>
    </filter-mapping>
    <!--
    注意:HiddenHttpMethodFilter必须作用于dispatcher前
    请求method支持 put 和 delete 必须添加该过滤器
    作用:可以过滤所有请求,并可以分为四种
    使用该过滤器需要在前端页面加隐藏表单域
    <input type="hidden" name="_method" value="请求方式(put/delete)">
    post会寻找_method中的请求式是不是put 或者 delete,如果不是 则默认post请求
    -->
    <!--
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <servlet-name>myAppServletName</servlet-name>
    </filter-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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--可以通过配置覆盖默认'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>myAppServletName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <!--  StandardServletMultipartResolver 属性配置  -->
        <!--<multipart-config>-->
            <!--上传到/tmp/upload 目录,如果配置为/使用HttpServletRequest上传时,可能会抛出异常/无权限操作-->
            <!--<location>/</location>-->
            <!--文件大小为2M-->
            <!--<max-file-size>2097152</max-file-size>-->
            <!--整个请求不超过4M-->
            <!--<max-request-size>4194304</max-request-size>-->
            <!--所有文件都要写入磁盘-->
            <!--<file-size-threshold>0</file-size-threshold>-->
        <!--</multipart-config>-->

    </servlet>
    <servlet-mapping>
        <servlet-name>myAppServletName</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

3)form表单

    <h2>Post包含上传文件提交:</h2>
    <form:form name="article" method="POST" action="update_with_post_file" modelAttribute="article" enctype="multipart/form-data">
       Id:<form:hidden path="id"/>
      Title: <form:input path="title" style="width:200px;"/><br/>
      Content: <form:input path="content" style="width:200px;"/><br/>
       yourfile: <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       yourfile2:
       <input type="file" name="execelFile"/><br/>
      <input type="submit" value="Submit" />
    </form:form>

4)后台接口

    @RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article,/*HttpServletRequest request*//*MultipartHttpServletRequest request*/DefaultMultipartHttpServletRequest request) throws IOException, ServletException {
        System.out.println(article);

        MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap();
        System.out.println("「");
        for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) {
            StringBuilder builder = new StringBuilder();

            part.getValue().forEach(s -> {
                builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],");
            });
            builder.delete(builder.length() - 1, builder.length());
            System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}");
        }
        System.out.println("」");

        String id = request.getParameter("id");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        System.out.println(String.format("%s,%s,%s", id, title, content));

        return "index";
    }

后台打印信息:

[DEBUG] Using MultipartResolver 'multipartResolver' for MultipartFilter
[DEBUG] Part 'files', size 1181 bytes, filename='ImageVO.java'
[DEBUG] Part 'files', size 1732 bytes, filename='UploadImgParam.java'
[DEBUG] Part 'files', size 9 bytes, filename='test.svg'
[DEBUG] Part 'execelFile', size 11375 bytes, filename='数据接口.xlsx'
[DEBUG] POST "/article/update_with_post_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_post_file(ArticleModel, DefaultMultipartHttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}
「
files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,test.svg,test.svg]}
execelFile->{execelFile,[execelFile,数据接口.xlsx,数据接口.xlsx]}
」
1,算法与数据结构--综合提升篇(c++版),文章内容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK
[DEBUG] Cleaning up part 'files', filename 'ImageVO.java'
[DEBUG] Cleaning up part 'files', filename 'UploadImgParam.java'
[DEBUG] Cleaning up part 'files', filename 'test.svg'
[DEBUG] Cleaning up part 'execelFile', filename '数据接口.xlsx'

Put方式:

只需要基于post方式,做以下调整即可:

1)/WEB-INF/web.xml中引入hiddenHttpMethodFilter(applicationContext.xml)不需要修改

<!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>
    <display-name>springmvcdemo</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <!--
    全局初始化数据,spring的监听器读取此配置文件,多个配置文件用分号分隔
    如果在web.xml中不写任何参数配置信息,默认的路径是/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml;
    如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
    -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 文件上传与下载过滤器:form表单中存在文件时,该过滤器可以处理http请求中的文件,被该过滤器过滤后会用post方法提交,form表单需设为enctype="multipart/form-data"-->
    <!-- 注意:必须放在HiddenHttpMethodFilter过滤器之前 -->
    <!--spring中配置的id为multipartResolver的解析器-->
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>-->
        <!--<url-pattern>/*</url-pattern>-->
        <servlet-name>myAppServletName</servlet-name>
    </filter-mapping>
    <!--
    注意:HiddenHttpMethodFilter必须作用于dispatcher前
    请求method支持 put 和 delete 必须添加该过滤器
    作用:可以过滤所有请求,并可以分为四种
    使用该过滤器需要在前端页面加隐藏表单域
    <input type="hidden" name="_method" value="请求方式(put/delete)">
    post会寻找_method中的请求式是不是put 或者 delete,如果不是 则默认post请求
    -->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <servlet-name>myAppServletName</servlet-name>
    </filter-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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--可以通过配置覆盖默认'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>myAppServletName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

        <!--  StandardServletMultipartResolver 属性配置  -->
        <!--<multipart-config>-->
            <!--上传到/tmp/upload 目录,如果配置为/使用HttpServletRequest上传时,可能会抛出异常/无权限操作-->
            <!--<location>/</location>-->
            <!--文件大小为2M-->
            <!--<max-file-size>2097152</max-file-size>-->
            <!--整个请求不超过4M-->
            <!--<max-request-size>4194304</max-request-size>-->
            <!--所有文件都要写入磁盘-->
            <!--<file-size-threshold>0</file-size-threshold>-->
        <!--</multipart-config>-->

    </servlet>
    <servlet-mapping>
        <servlet-name>myAppServletName</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

2)post表单中引入<input type="hidden" name="_method" value="put" />标签

    <h2>Put包含上传文件提交:</h2>
    <form method="POST" name="article" action="update_with_put_file" enctype="multipart/form-data">
        <input type="hidden" name="_method" value="PUT"/>
        Id:<input name="id" id="id" value="${article.id}"/><br/>
        Title:<input name="title" id="title" value="${article.title}"/><br/>
        Content:<input name="content" id="content" value="${article.content}"/><br/>
       yourfile: <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       <input type="file" name="files"/><br/>
       yourfile2:
       <input type="file" name="execelFile"/><br/>
        <input type="submit" value="Submit" />
    </form>

3)后台接口修改put方式接收请求

    @RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, /*HttpServletRequest request*//*MultipartHttpServletRequest request*/DefaultMultipartHttpServletRequest request) throws IOException, ServletException {
        System.out.println(article);

        MultiValueMap<String, MultipartFile> parts = request.getMultiFileMap();
        System.out.println("「");
        for (Map.Entry<String, List<MultipartFile>> part : parts.entrySet()) {
            StringBuilder builder = new StringBuilder();

            part.getValue().forEach(s -> {
                builder.append("[" + s.getName() + "," + s.getOriginalFilename() + "," + s.getOriginalFilename() + "],");
            });
            builder.delete(builder.length() - 1, builder.length());
            System.out.println(part.getKey() + "->{" + part.getKey() + "," + builder.toString() + "}");
        }
        System.out.println("」");

        String id = request.getParameter("id");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        System.out.println(String.format("%s,%s,%s", id, title, content));

        return "index";
    }

后台打印信息:

[DEBUG] Using MultipartResolver 'multipartResolver' for MultipartFilter
[DEBUG] Part 'files', size 1181 bytes, filename='ImageVO.java'
[DEBUG] Part 'files', size 1732 bytes, filename='UploadImgParam.java'
[DEBUG] Part 'files', size 13872 bytes, filename='SpringBoot-Converter'
[DEBUG] Part 'execelFile', size 11375 bytes, filename='数据接口.xlsx'
[DEBUG] PUT "/article/update_with_put_file", parameters={masked}
[DEBUG] Mapped to com.dx.test.controller.ArticleController#update_with_put_file(ArticleModel, DefaultMultipartHttpServletRequest)
ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}
「
files->{files,[files,ImageVO.java,ImageVO.java],[files,UploadImgParam.java,UploadImgParam.java],[files,SpringBoot-Converter,SpringBoot-Converter]}
execelFile->{execelFile,[execelFile,数据接口.xlsx,数据接口.xlsx]}
」
1,算法与数据结构--综合提升篇(c++版),文章内容
[DEBUG] View name 'index', model {article=ArticleModel{id=1, categoryId=null, title='算法与数据结构--综合提升篇(c++版)', content='文章内容'}, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
[DEBUG] Forwarding to [/WEB-INF/views/index.jsp]
[DEBUG] Completed 200 OK
[DEBUG] Cleaning up part 'files', filename 'ImageVO.java'
[DEBUG] Cleaning up part 'files', filename 'UploadImgParam.java'
[DEBUG] Cleaning up part 'files', filename 'SpringBoot-Converter'
[DEBUG] Cleaning up part 'execelFile', filename '数据接口.xlsx'

 

posted @ 2019-11-02 19:14  cctext  阅读(2802)  评论(1编辑  收藏  举报