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

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

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

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

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'

SpringMVC(十五):Dispatcher的重要组件之一MultipartResolver(StandardServletMultipartResolver和CommonsMultipartResolver)的用法的更多相关文章

  1. SpringMVC(十五) RequestMapping map模型数据

    控制器中使用map模型数据,传送数据给视图. 控制器参考代码: package com.tiekui.springmvc.handlers; import java.util.Arrays; impo ...

  2. 25、手把手教你Extjs5(二十五)Extjs5常用组件--form的基本用法

    Extjs Form是一个比较常用的控件,主要用来显示和编辑数据的,今天这篇文章将介绍Extjs Form控件的详细用法,包括创建Form.添加子项.加载和更新数据.验证等. Form和Form Ba ...

  3. Android笔记(五十五) Android四大组件之一——ContentProvider,使用系统提供的ContentProvider

    因为在Android中,存储系统联系人姓名和电话是存在与不同的ContentProvider中的,具体如何查找,可以从Android的源代码中查看,在android.providers包中列出了所有系 ...

  4. Bootstrap入门(十五)组件9:面板组件

    Bootstrap入门(十五)组件9:面板组件 虽然不总是必须,但是某些时候你可能需要将某些 DOM 内容放到一个盒子里.对于这种情况,可以试试面板组件. 1.基本实例 2.带标题的面板 3.情景效果 ...

  5. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十五)阶段总结

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 一 每个阶段在结尾时都会有一个阶段总结,在<SSM整合基础篇& ...

  6. {Django基础十之Form和ModelForm组件}一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 ModelForm

    Django基础十之Form和ModelForm组件 本节目录 一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 Model ...

  7. 微信小程序把玩(二十五)loading组件

    原文:微信小程序把玩(二十五)loading组件 loading通常使用在请求网络数据时的一种方式,通过hidden属性设置显示与否 主要属性: wxml <!----> <butt ...

  8. 微信小程序把玩(十五)checkbox组件

    原文:微信小程序把玩(十五)checkbox组件 不得不吐糟下checkbox默认样式真是有点略丑!!!checkbox组件为一个多选框被放到checkbox-group组中,并在checkbox-g ...

  9. Bootstrap<基础十五> 输入框组

    Bootstrap 支持的另一个特性,输入框组.输入框组扩展自 表单控件.使用输入框组,可以很容易地向基于文本的输入框添加作为前缀和后缀的文本或按钮. 通过向输入域添加前缀和后缀的内容,您可以向用户输 ...

随机推荐

  1. 记录下Hbuilder 打包IOS发布时 总是提示错误:ios prifile文件与私钥证书匹配 的问题

    最近两天,新的APP准备要上线,然后打包正式发布版 时,总是提示不匹配 证书照hbuilder里面的文档 一样也不行,然后百度了N种方法,都是不行,而且也比较少搜索到相关问题. 后来都是谷歌了下,找到 ...

  2. android中listview滑动卡顿的原因

    导致Android界面滑动卡顿主要有两个原因: 1.UI线程(main)有耗时操作 2.视图渲染时间过长,导致卡顿 http://www.tuicool.com/articles/fm2IFfU 

  3. linux 命令输出保存为文件的三种方式

    一.ls >2.txt        将ls命令直接保存到home文件夹下的2.txt,命令窗口无显示 二.ls | tee 2.txt    也是直接保存在了home文件夹下的2.txt,命令 ...

  4. 第十周LINUX 学习笔记

    LVS集群nat丶DR HA:高可用    平均无故障时间/(平均无故障时间+平均修复时间)        负载均衡 次序lb(负载)——>ha()LB  tcp:lvs,haproxy  应用 ...

  5. 洛谷P4180 【模板】严格次小生成树[BJWC2010] 题解

    虽然中途写的时候有点波折,但是最后一发A,还是有点爽. 这虽然是个模板题,但还是涉及到许多知识的= = 首先我们求出一个最小生成树,并且求出其边权和\(ans\).那么现在考虑加入其它的边,每次加入在 ...

  6. Jmeter跨线程组传递cookie,以禅道系统为例;BeanShell的存取数据的使用

    先看下脚本结构: 思路:将登陆请求放在setUp Thread Group中:把登陆后的cookie通过正则提取出来,然后存为全局变量,传递到下一个线程组中: 第一步:添加setUp Thread G ...

  7. httprunner学习21-extentreports页面样式无法加载问题(已解决)

    前言 最近有小伙伴反应使用httprunner的extentreports报告时,打开的页面样式全部丢失了,原本高大上的报告变成了丑八怪. 顿时心都凉了一大截,要是让领导看到了,这个月领导不给加鸡腿了 ...

  8. C#多线程编程中的锁系统

    C#多线程编程中的锁系统(二) 上章主要讲排他锁的直接使用方式.但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了. 这一次我们说说升级锁和原子操作. 目录 1:volatile 2:  Inter ...

  9. 图论 - PAT甲级 1003 Emergency C++

    PAT甲级 1003 Emergency C++ As an emergency rescue team leader of a city, you are given a special map o ...

  10. POJ 3322 Bloxorz

    #include<cstring> #include<algorithm> #include<iostream> #include<cstdio> #i ...