SpringMVC(十五):Dispatcher的重要組件之一MultipartResolver(StandardServletMultipartResolver和CommonsMultipartRe

MultipartResolver組件

從Spring官網上能夠看到MultipartResolver接口的定義信息:html

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.java

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.c++

 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.web

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.spring

關於MultipartResolver的接口文檔,請參考Spring5.2.x的官方文檔《https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/MultipartResolver.htmlapache

文件上傳策略接口MultipartResolver的實現

public interface MultipartResolver {
    // 判斷request是否爲文件上傳請求
    boolean isMultipart(HttpServletRequest request);

    // 將HttpServletRequest請求轉化爲MultipartHttpServletRequest
    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

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

當用戶的請求到DispatcherServlet時,json

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'
相關文章
相關標籤/搜索