自动配置
@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)@ConditionalOnWebApplication(type = Type.SERVLET)@EnableConfigurationProperties(MultipartProperties.class)public class MultipartAutoConfiguration {private final MultipartProperties multipartProperties;public MultipartAutoConfiguration(MultipartProperties multipartProperties) {this.multipartProperties = multipartProperties;}@Bean@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })public MultipartConfigElement multipartConfigElement() {return this.multipartProperties.createMultipartConfig();}@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)@ConditionalOnMissingBean(MultipartResolver.class)public StandardServletMultipartResolver multipartResolver() {StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());return multipartResolver;}}
MultipartConfigElement
看看谁用到了这个类
居然是StandardServletMultipartResolver的注释说到了它.
Standard implementation of the MultipartResolver interface, based on the Servlet 3.0 Part API. To be added as “multipartResolver” bean to a Spring DispatcherServlet context, without any extra configuration at the bean level (see below).
Note: In order to use Servlet 3.0 based multipart parsing, you need to
- mark the affected servlet with a “multipart-config” section in web.xml,
- or with a javax.servlet.MultipartConfigElement in programmatic servlet registration,
- or (in case of a custom servlet class) possibly with a javax.servlet.annotation.MultipartConfig annotation on your servlet class.
Configuration settings such as maximum sizes or storage locations need to be applied at that servlet registration level; Servlet 3.0 does not allow for them to be set at the MultipartResolver level
相当于向servlet注册一个上传的配置,做法就是multipartProperties->MultipartConfigElement,属性映射
翻译过来就是
- 可以通过xml或者注解->注册servlet
或者通过配置属性,让springmvc的dispatchServlet具备这个上传配置
StandardServletMultipartResolver
代码流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;try {ModelAndView mv = null;Exception dispatchException = null;try {//逻辑this.multipartResolver.resolveMultipart(request)->new StandardMultipartHttpServletRequest(request, this.resolveLazily)//具体看下面代码,不是懒加载的话就直接解析文件流processedRequest = checkMultipart(request);//truemultipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.真正执行的逻辑,包括参数的确定和返回值计算,关键在于参数确定,见下面mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}
解析文件流
```java public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
@Nullable private MultiValueMap
multipartFiles; //构造器 根据懒加载配置,看看是否进行解析 public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {super(request);if (!lazyParsing) {parseRequest(request);}
} //解析逻辑 private void parseRequest(HttpServletRequest request) {
try {//servlet3.0原生api,直接从request中获取文件流,封装为PartCollection<Part> parts = request.getParts();this.multipartParameterNames = new LinkedHashSet<>(parts.size());//封装到单key多value的Map中去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());}}//最终封装的放在这个request中的multipartFiles属性setMultipartFiles(files);}catch (Throwable ex) {handleParseFailure(ex);}
} /**
- Obtain the MultipartFile Map for retrieval,
- lazily initializing it if necessary.
- @see #initializeMultipart()
*/
protected MultiValueMap
getMultipartFiles() { //根据属性,如果没有就加载 //懒加载true:则初始化的时候没有准备这个值,那么就是空的,就要初始化 //懒加载false:已经初始化好了这个属性,直接就可以返回这个属性 if (this.multipartFiles == null) {
} return this.multipartFiles; }//真正逻辑在parseRequest(getRequest()),跟构造器那个同样的方法initializeMultipart();
}
<a name="C0E0h"></a>## 确定参数RequestMappingHandlerAdapter<br />#方法调用栈<br />handle<br />handleInternal<br />invokeHandlerMethod<br />new ServletInvocableHandlerMethod()-->设置参数-->invocableMethod.invokeAndHandle**(**webRequest, mavContainer**)**;<br />ServletInvocableHandlerMethod<br />#方法调用栈<br />invokeAndHandle<br />invokeForRequest<br />getMethodArgumentValues<br />策略模式的应用```javapublic class InvocableHandlerMethod extends HandlerMethodprivate HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}//核心代码1:RequestPartMethodArgumentResolver#supportsParameter->getArgumentResolver(1遍历 2缓存 3break)//逻辑:parameter.hasParameterAnnotation(RequestPart.class)->trueif (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {//核心代码2:RequestPartMethodArgumentResolver#resolveArgument->getArgumentResolver(1遍历 2缓存 3break)resolver.resolveArgument//resolver.resolveArgument逻辑要看代码args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}
上传文件请求参数解析器RequestPartMethodArgumentResolver
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);Assert.state(servletRequest != null, "No HttpServletRequest");RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());String name = getPartName(parameter, requestPart);parameter = parameter.nestedIfOptional();Object arg = null;//逻辑代码Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {arg = mpArg;}return adaptArgumentIfNecessary(arg, parameter);}
干活的人MultipartResolutionDelegate,文件流已经封装在request里面的,何时封装的呢?就在上面的解析文件流中
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)throws Exception {MultipartHttpServletRequest multipartRequest =WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);boolean isMultipart = (multipartRequest != null || isMultipartContent(request));//单文件if (MultipartFile.class == parameter.getNestedParameterType()) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}return multipartRequest.getFile(name);}//文件集合Collectionelse if (isMultipartFileCollection(parameter)) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}List<MultipartFile> files = multipartRequest.getFiles(name);return (!files.isEmpty() ? files : null);}//文件数组else if (isMultipartFileArray(parameter)) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}List<MultipartFile> files = multipartRequest.getFiles(name);return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);}.......else {return UNRESOLVABLE;}}
总结
springmvc在servlet3.0上做的封装不多
multipartRequest.getFile
multipartRequest.getFiles(name)
解析的文件流存放位置:
用的时候根据MultipartFile接口的api去使用即可,
根据参数名,操作不同的流,或者是同名参数的多个流操作
设计还是不错的
