该系列文档是本人在学习 Spring MVC 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读html
Spring 版本:5.2.4.RELEASEjava
该系列其余文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》git
MultipartResolver
组件,内容类型( Content-Type
)为 multipart/*
的请求的解析器,主要解析文件上传的请求。例如,MultipartResolver
会将 HttpServletRequest 封装成 MultipartHttpServletRequest
对象,便于获取参数信息以及上传的文件github
使用方式,能够参考《MyBatis 使用手册》中的 集成 Spring 模块下的 spring-mvc.xml
文件中配置 MultipartResolver
为 CommonsMultipartResolver
实现类,而后在方法入参中用 MultipartFile 类型接收web
关于在 SpringBoot 中如何使用文件上传可参考 Spring 官方文档spring
先来回顾一下在 DispatcherServlet
中处理请求的过程当中哪里使用到 MultipartResolver
组件,能够回到《一个请求的旅行过程》中的 DispatcherServlet
的 doDispatch
方法中看看,以下:apache
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // ... 省略相关代码 // <2> 检测请求是否为上传请求,若是是则经过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象 processedRequest = checkMultipart(request); // ... 省略相关代码 } protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // 若是该请求是一个涉及到 multipart (文件)的请求 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } else if (hasMultipartException(request)) { logger.debug("Multipart resolution previously failed for current request - " + "skipping re-resolution for undisturbed error rendering"); } else { try { // 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件 return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } // If not returned before: return original request. return request; }
<2>
处,若是该请求是一个涉及到 multipart (文件)的请求,则经过 multipartResolver
将 HttpServletRequest
请求封装成 MultipartHttpServletRequest
对象,解析请求里面的参数以及文件数组
org.springframework.web.multipart.MultipartResolver
接口,内容类型( Content-Type
)为 multipart/*
的请求的解析器接口,代码以下:spring-mvc
public interface MultipartResolver { /** * 是否为 multipart 请求 */ boolean isMultipart(HttpServletRequest request); /** * 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象 */ MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; /** * 清理处理 multipart 产生的资源,例如临时文件 */ void cleanupMultipart(MultipartHttpServletRequest request); }
MultipartResolver 接口体系的结构以下:mvc
一共有两块:
在 DispatcherServlet
的 initMultipartResolver(ApplicationContext context)
方法,初始化 MultipartResolver 组件,方法以下:
private void initMultipartResolver(ApplicationContext context) { try { // 从 Spring 上下文中获取名称为 "multipartResolver" ,类型为 MultipartResolver 的 Bean this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.multipartResolver); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; if (logger.isTraceEnabled()) { logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared"); } } }
在 Spring MVC 中,multipartResolver
默认为 null
【注意】,须要本身配置,例如《MyBatis 使用手册》中的 集成 Spring 模块下的 spring-mvc.xml
文件中配置 MultipartResolver 为 CommonsMultipartResolver
实现类,也能够配置为 StandardServletMultipartResolver
实现类
在 Spring Boot 中,multipartResolver
默认为 StandardServletMultipartResolver
实现类
目前 Spring 只提供上面两种实现类,接下来依次进行分析
org.springframework.web.multipart.support.StandardServletMultipartResolver
,实现 MultipartResolver 接口,基于 Servlet 3.0 标准的上传文件 API 的 MultipartResolver 实现类,代码以下:
public class StandardServletMultipartResolver implements MultipartResolver { /** * 是否延迟解析 */ private boolean resolveLazily = false; public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } @Override public boolean isMultipart(HttpServletRequest request) { // 请求的 Content-type 必须 multipart/ 开头 return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/"); } @Override public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); } @Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { // To be on the safe side: explicitly delete the parts, // but only actual file parts (for Resin compatibility) try { // 删除临时的 Part for (Part part : request.getParts()) { if (request.getFile(part.getName()) != null) { part.delete(); } } } catch (Throwable ex) { LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex); } } } }
isMultipart(HttpServletRequest request)
方法,请求的 Content-type 是否以 multipart/
开头
resolveMultipart(HttpServletRequest request)
方法,直接将 HttpServletRequest 转换成 StandardMultipartHttpServletRequest
对象
cleanupMultipart(MultipartHttpServletRequest request)
方法,清理资源,删除临时的 javax.servlet.http.Part
们
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest
,继承 AbstractMultipartHttpServletRequest 抽象类,基于 Servlet 3.0 的 Multipart HttpServletRequest 实现类,包含了一个 javax.servlet.http.HttpServletRequest
对象和它的 javax.servlet.http.Part
对象们,其中 Part 对象会被封装成 StandardMultipartFile
对象
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { /** * 普通参数名的集合 */ @Nullable private Set<String> multipartParameterNames; public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException { this(request, false); } public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); // 若是不须要延迟解析 if (!lazyParsing) { // 解析请求 parseRequest(request); } } }
multipartParameterNames
:普通参数名的集合,非上传文件的参数名parseRequest(HttpServletRequest request)
方法,直接解析请求parseRequest(HttpServletRequest request)
方法,解析请求,解析 HttpServletRequest
中的 Part
对象,若是是文件,则封装成 StandardMultipartFile
对象,不然就是普通参数,获取其名称,以下:
private void parseRequest(HttpServletRequest request) { try { // <1> 从 HttpServletRequest 中获取 Part 们 Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size()); // <2> 遍历 parts 数组 for (Part part : parts) { // <2.1> 得到请求头中的 Content-Disposition 信息,MIME 协议的扩展 String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION); // <2.2> 对 Content-Disposition 信息进行解析,生成 ContentDisposition 对象 // 包含请求参数信息,以面向“对象”的形式进行访问 ContentDisposition disposition = ContentDisposition.parse(headerValue); // <2.3> 得到文件名 String filename = disposition.getFilename(); // <2.4> 状况一,文件名非空,说明是文件参数,则建立 StandardMultipartFile 对象 if (filename != null) { if (filename.startsWith("=?") && filename.endsWith("?=")) { filename = MimeDelegate.decode(filename); } files.add(part.getName(), new StandardMultipartFile(part, filename)); } // <2.5> 状况二,文件名为空,说明是普通参数,则保存参数名称 else { this.multipartParameterNames.add(part.getName()); } } // <3> 将上面生成的 StandardMultipartFile 文件对象们,设置到父类的 multipartFiles 属性中 setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } }
从 HttpServletRequest 中获取 Part 们
遍历 parts
数组
Content-Disposition
信息,MIME 协议的扩展ContentDisposition
对象,包含请求参数信息,以面向“对象”的形式进行访问ContentDisposition
对象中得到文件名StandardMultipartFile
对象将上面生成的 StandardMultipartFile
文件对象们,设置到父类的 multipartFiles
属性中
若是发生异常则抛出
/** 初始化请求 */ @Override protected void initializeMultipart() { parseRequest(getRequest()); } /** 获取请求中的参数名称 */ @Override public Enumeration<String> getParameterNames() { if (this.multipartParameterNames == null) { initializeMultipart(); } if (this.multipartParameterNames.isEmpty()) { return super.getParameterNames(); } // Servlet 3.0 getParameterNames() not guaranteed to include multipart form items // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side Set<String> paramNames = new LinkedHashSet<>(); Enumeration<String> paramEnum = super.getParameterNames(); while (paramEnum.hasMoreElements()) { paramNames.add(paramEnum.nextElement()); } paramNames.addAll(this.multipartParameterNames); return Collections.enumeration(paramNames); } /** 获取请求中的参数,参数名和参数值的映射 */ @Override public Map<String, String[]> getParameterMap() { if (this.multipartParameterNames == null) { initializeMultipart(); } if (this.multipartParameterNames.isEmpty()) { return super.getParameterMap(); } // Servlet 3.0 getParameterMap() not guaranteed to include multipart form items // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side Map<String, String[]> paramMap = new LinkedHashMap<>(super.getParameterMap()); for (String paramName : this.multipartParameterNames) { if (!paramMap.containsKey(paramName)) { paramMap.put(paramName, getParameterValues(paramName)); } } return paramMap; } /** 获取请求的 Content-Type 内容类型 */ @Override public String getMultipartContentType(String paramOrFileName) { try { Part part = getPart(paramOrFileName); return (part != null ? part.getContentType() : null); } catch (Throwable ex) { throw new MultipartException("Could not access multipart servlet request", ex); } } /** 获取请求头信息 */ @Override public HttpHeaders getMultipartHeaders(String paramOrFileName) { try { Part part = getPart(paramOrFileName); if (part != null) { HttpHeaders headers = new HttpHeaders(); for (String headerName : part.getHeaderNames()) { headers.put(headerName, new ArrayList<>(part.getHeaders(headerName))); } return headers; } else { return null; } } catch (Throwable ex) { throw new MultipartException("Could not access multipart servlet request", ex); } }
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest
的私有内部静态类,实现了 MultipartFile
接口和 Serializable
接口,内部封装了 javax.servlet.http.Part
对象和文件名称,代码以下:
private static class StandardMultipartFile implements MultipartFile, Serializable { private final Part part; private final String filename; public StandardMultipartFile(Part part, String filename) { this.part = part; this.filename = filename; } @Override public String getName() { return this.part.getName(); } @Override public String getOriginalFilename() { return this.filename; } @Override public String getContentType() { return this.part.getContentType(); } @Override public boolean isEmpty() { return (this.part.getSize() == 0); } @Override public long getSize() { return this.part.getSize(); } @Override public byte[] getBytes() throws IOException { return FileCopyUtils.copyToByteArray(this.part.getInputStream()); } @Override public InputStream getInputStream() throws IOException { return this.part.getInputStream(); } @Override public void transferTo(File dest) throws IOException, IllegalStateException { this.part.write(dest.getPath()); if (dest.isAbsolute() && !dest.exists()) { // Servlet 3.0 Part.write is not guaranteed to support absolute file paths: // may translate the given path to a relative location within a temp dir // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths). // At least we offloaded the file from memory storage; it'll get deleted // from the temp dir eventually in any case. And for our user's purposes, // we can manually copy it to the requested location as a fallback. FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath())); } } @Override public void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest)); } }
这个类封装了 Servlet 3.0 的 Part
对象,也就是咱们经常使用到的 MultipartFile 对象,支持对文件的操做,内部其实都是调用 javax.servlet.http.Part
的方法
org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest
抽象类,继承了 HttpServletRequestWrapper 类,实现了 MultipartHttpServletRequest接口
该类是 StandardMultipartHttpServletRequest
和 DefaultMultipartHttpServletRequest
的父类,实现了一些公共的方法,代码以下:
public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest { /** * 请求中的文件信息 */ @Nullable private MultiValueMap<String, MultipartFile> multipartFiles; protected AbstractMultipartHttpServletRequest(HttpServletRequest request) { super(request); } @Override public HttpServletRequest getRequest() { return (HttpServletRequest) super.getRequest(); } @Override public HttpMethod getRequestMethod() { return HttpMethod.resolve(getRequest().getMethod()); } /** 获取请求头信息 */ @Override public HttpHeaders getRequestHeaders() { HttpHeaders headers = new HttpHeaders(); Enumeration<String> headerNames = getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.put(headerName, Collections.list(getHeaders(headerName))); } return headers; } /** 获取文件名称列表 */ @Override public Iterator<String> getFileNames() { return getMultipartFiles().keySet().iterator(); } /** 获取指定文件名的单个文件 */ @Override public MultipartFile getFile(String name) { return getMultipartFiles().getFirst(name); } /** 获取指定文件名的多个文件 */ @Override public List<MultipartFile> getFiles(String name) { List<MultipartFile> multipartFiles = getMultipartFiles().get(name); if (multipartFiles != null) { return multipartFiles; } else { return Collections.emptyList(); } } @Override public Map<String, MultipartFile> getFileMap() { return getMultipartFiles().toSingleValueMap(); } @Override public MultiValueMap<String, MultipartFile> getMultiFileMap() { return getMultipartFiles(); } public boolean isResolved() { return (this.multipartFiles != null); } protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) { this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles)); } protected MultiValueMap<String, MultipartFile> getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } return this.multipartFiles; } /** 交由子类实现 */ protected void initializeMultipart() { throw new IllegalStateException("Multipart request not initialized"); } }
上面的方法都比较简单,用于获取请求中的文件对象
MultiValueMap<String, MultipartFile> multipartFiles
属性,保存由子类解析出请求中的 Part 对象所封装成的 MultipartFile 对象
org.springframework.web.multipart.commons.CommonsMultipartResolver
,实现 MultipartResolver、ServletContextAware 接口,继承 CommonsFileUploadSupport 抽象类,基于 Apache Commons FileUpload 的 MultipartResolver 实现类
若是须要使用这个 MultipartResolver 实现类,须要引入 commons-fileupload
、commons-io
和 commons-codec
组件,例如:
<dependencies> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> </dependencies>
注意,若是 Spring Boot 项目中须要使用 CommonsMultipartResolver,须要在 application.yml 中添加以下配置,排除其默认的配置,以下:
spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware { /** * 是否延迟解析 */ private boolean resolveLazily = false; public CommonsMultipartResolver() { super(); } public CommonsMultipartResolver(ServletContext servletContext) { this(); setServletContext(servletContext); } }
@Override public boolean isMultipart(HttpServletRequest request) { // 必须是 POST 请求,且 Content-Type 为 multipart/ 开头 return ServletFileUpload.isMultipartContent(request); }
判断是否为 multipart 请求,必须是 POST
请求,且 Content-Type 为 multipart/
开头
@Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override protected void initializeMultipart() { // 解析请求,获取文件、参数信息 MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { // 解析请求,获取文件、参数信息 MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } }
将 HttpServletRequest 转换成 DefaultMultipartHttpServletRequest
对象
若是开启了延迟解析,则重写该对象的 initializeMultipart() 方法,用于解析请求
不然直接调用 parseRequest(HttpServletRequest request)
方法解析请求,返回 MultipartParsingResult 对象,包含 MultipartFile 对象和普通参数信息
parseRequest(HttpServletRequest request)
方法,用于解析请求,返回 MultipartParsingResult 对象,包含 MultipartFile 对象、普通参数信息以及参数的 Content-Type 信息,方法以下:
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { // <1> 获取请求中的编码 String encoding = determineEncoding(request); // <2> 获取 ServletFileUpload 对象 FileUpload fileUpload = prepareFileUpload(encoding); try { // <3> 获取请求中的流数据 List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); // <4> 将这些流数据转换成 MultipartParsingResult,包含 CommonsMultipartFile、参数信息、Content-type return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Failed to parse multipart servlet request", ex); } }
获取请求中的编码
根据编码获取到 ServletFileUpload 对象( commons-fileupload
中的类),在 newFileUpload(FileItemFactory fileItemFactory)
方法中返回的就是 ServletFileUpload 对象,能够看到父类 CommonsFileUploadSupport 的构造方法,以下:
// org.springframework.web.multipart.commons.CommonsFileUploadSupport.java public CommonsFileUploadSupport() { this.fileItemFactory = newFileItemFactory(); // 由子类实现 this.fileUpload = newFileUpload(getFileItemFactory()); }
具体细节就不讲述了
经过 ServletFileUpload 对象解析请求,返回流数据 List<FileItem> fileItems
调用父类 CommonsFileUploadSupport 的 parseFileItems(List<FileItem> fileItems, String encoding)
方法,将这些流数据转换成 MultipartParsingResult 对象
// org.springframework.web.multipart.commons.CommonsFileUploadSupport.java protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) { MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>(); Map<String, String[]> multipartParameters = new HashMap<>(); Map<String, String> multipartParameterContentTypes = new HashMap<>(); // Extract multipart files and multipart parameters. for (FileItem fileItem : fileItems) { if (fileItem.isFormField()) { String value; String partEncoding = determineEncoding(fileItem.getContentType(), encoding); try { value = fileItem.getString(partEncoding); } catch (UnsupportedEncodingException ex) { if (logger.isWarnEnabled()) { logger.warn("Could not decode multipart item '" + fileItem.getFieldName() + "' with encoding '" + partEncoding + "': using platform default"); } value = fileItem.getString(); } String[] curParam = multipartParameters.get(fileItem.getFieldName()); if (curParam == null) { // simple form field multipartParameters.put(fileItem.getFieldName(), new String[] {value}); } else { // array of simple form fields String[] newParam = StringUtils.addStringToArray(curParam, value); multipartParameters.put(fileItem.getFieldName(), newParam); } multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType()); } else { // multipart file field CommonsMultipartFile file = createMultipartFile(fileItem); multipartFiles.add(file.getName(), file); LogFormatUtils.traceDebug(logger, traceOn -> "Part '" + file.getName() + "', size " + file.getSize() + " bytes, filename='" + file.getOriginalFilename() + "'" + (traceOn ? ", storage=" + file.getStorageDescription() : "") ); } } return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes); }
大体就是遍历 fileItems
集合,若是是一个简单的表单字段,那么就是一个普通的参数,将参数名和值保存起来
不然就是文件,将其封装成 CommonsMultipartFile
保存起来
cleanupMultipart(MultipartHttpServletRequest request)
方法,清理文件产生的临时资源,以下:
// CommonsMultipartResolver.java @Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { try { cleanupFileItems(request.getMultiFileMap()); } catch (Throwable ex) { logger.warn("Failed to perform multipart cleanup for servlet request", ex); } } } // CommonsFileUploadSupport.java protected void cleanupFileItems(MultiValueMap<String, MultipartFile> multipartFiles) { for (List<MultipartFile> files : multipartFiles.values()) { for (MultipartFile file : files) { if (file instanceof CommonsMultipartFile) { CommonsMultipartFile cmf = (CommonsMultipartFile) file; cmf.getFileItem().delete(); LogFormatUtils.traceDebug(logger, traceOn -> "Cleaning up part '...")); } } } }
org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest
,继承 AbstractMultipartHttpServletRequest 抽象类,MultipartHttpServletRequest 的默认实现类,代码以下:
public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { private static final String CONTENT_TYPE = "Content-Type"; @Nullable private Map<String, String[]> multipartParameters; @Nullable private Map<String, String> multipartParameterContentTypes; public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) { super(request); setMultipartFiles(mpFiles); setMultipartParameters(mpParams); setMultipartParameterContentTypes(mpParamContentTypes); } public DefaultMultipartHttpServletRequest(HttpServletRequest request) { super(request); } @Override @Nullable public String getParameter(String name) { String[] values = getMultipartParameters().get(name); if (values != null) { return (values.length > 0 ? values[0] : null); } return super.getParameter(name); } @Override public String[] getParameterValues(String name) { String[] parameterValues = super.getParameterValues(name); String[] mpValues = getMultipartParameters().get(name); if (mpValues == null) { return parameterValues; } if (parameterValues == null || getQueryString() == null) { return mpValues; } else { String[] result = new String[mpValues.length + parameterValues.length]; System.arraycopy(mpValues, 0, result, 0, mpValues.length); System.arraycopy(parameterValues, 0, result, mpValues.length, parameterValues.length); return result; } } @Override public Enumeration<String> getParameterNames() { Map<String, String[]> multipartParameters = getMultipartParameters(); if (multipartParameters.isEmpty()) { return super.getParameterNames(); } Set<String> paramNames = new LinkedHashSet<>(); paramNames.addAll(Collections.list(super.getParameterNames())); paramNames.addAll(multipartParameters.keySet()); return Collections.enumeration(paramNames); } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> result = new LinkedHashMap<>(); Enumeration<String> names = getParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); result.put(name, getParameterValues(name)); } return result; } @Override public String getMultipartContentType(String paramOrFileName) { MultipartFile file = getFile(paramOrFileName); if (file != null) { return file.getContentType(); } else { return getMultipartParameterContentTypes().get(paramOrFileName); } } @Override public HttpHeaders getMultipartHeaders(String paramOrFileName) { String contentType = getMultipartContentType(paramOrFileName); if (contentType != null) { HttpHeaders headers = new HttpHeaders(); headers.add(CONTENT_TYPE, contentType); return headers; } else { return null; } } protected final void setMultipartParameters(Map<String, String[]> multipartParameters) { this.multipartParameters = multipartParameters; } protected Map<String, String[]> getMultipartParameters() { if (this.multipartParameters == null) { initializeMultipart(); } return this.multipartParameters; } protected final void setMultipartParameterContentTypes(Map<String, String> multipartParameterContentTypes) { this.multipartParameterContentTypes = multipartParameterContentTypes; } protected Map<String, String> getMultipartParameterContentTypes() { if (this.multipartParameterContentTypes == null) { initializeMultipart(); } return this.multipartParameterContentTypes; } }
代码并不复杂,稍微阅读一下就理解了😈
本文对 Spring MVC 处理请求的过程当中使用到的 MultipartResolver 组件进行了分析,若是请求的 Content-Type
为 multipart/*
,涉及到文件上传,因此处理请求的第一步须要经过 MultipartResolver 组件对请求进行转换处理。会将 HttpServletRequest
请求对象封装成 MultipartHttpServletRequest
对象,便于获取参数信息和操做上传的文件(MultipartFile 对象)。
MultipartResolver 组件的实现类有两种:
org.springframework.web.multipart.support.StandardServletMultipartResolver
:实现 MultipartResolver 接口,基于 Servlet 3.0 标准的上传文件 API 的 MultipartResolver 实现类org.springframework.web.multipart.commons.CommonsMultipartResolver
:实现 MultipartResolver 接口,基于 Apache Commons FileUpload 的 MultipartResolver 实现类二者的区别:
StandardServletMultipartResolver 会将 HttpServletRequest 封装成 StandardMultipartHttpServletRequest
对象,由 Servlet 3.0 提供 API 获取请求中的 javax.servlet.http.Part
对象,而后进行解析,文件会封装成 StandardMultipartFile
对象
CommonsMultipartResolver 会将 HttpServletRequest 封装成 DefaultMultipartHttpServletRequest
对象,由 Apache 的 Commons FileUpload 组件来实现,经过 org.apache.commons.fileupload.servlet.ServletFileUpload
对象获取请求中的 org.apache.commons.fileupload.FileItem
对象,而后进行解析,文件会封装成 CommonsMultipartFile
对象,如何使用能够参考上面的 CommonsMultipartResolver 小节
注意事项:
multipartResolver
默认为 null
,须要本身配置,例如《MyBatis 使用手册》中的 集成 Spring 模块下的 spring-mvc.xml
文件中配置 MultipartResolver 为 CommonsMultipartResolver
实现类,也能够配置为 StandardServletMultipartResolver
实现类multipartResolver
默认为 StandardServletMultipartResolver
实现类参考文章:芋道源码《精尽 Spring MVC 源码分析》