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'

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM