SpringMVC解析文件参数过程
我们知道SpringMVC 接收文件的时候直接用一个MultipartFile 接收即可,但是SpringMVC是如何解析以及如何绑定到参数的不清楚。
1. SpringMVC接收文件的接口如下
@RequestMapping("/upload") @ResponseBody public Map<String, Object> uploadPicture(MultipartFile imgFile) { Map<String, Object> result = new HashMap<String, Object>(); result.put("error", 1); if (imgFile == null) { result.put("message", "文件没接到"); return result; } logger.debug("file -> {},viewId ->{}", imgFile.getOriginalFilename()); String fileOriName = imgFile.getOriginalFilename();// 获取原名称 String fileNowName = UUIDUtils.getUUID2() + "." + FilenameUtils.getExtension(fileOriName);// 生成唯一的名字 try { FileHandleUtil.uploadSpringMVCFile(imgFile, fileNowName); } catch (Exception e) { logger.error("uploadPicture error", e); return result; } String id = UUIDUtils.getUUID(); Document document = new Document(); document.setCreatetime(new Date()); document.setPath(fileNowName); document.setId(id); document.setOriginName(fileOriName); document.setUploaderUsername(MySystemUtils.getLoginUsername()); documentService.insert(document); // 回传JSON结果 result.put("error", 0); result.put("url", "/document/getDocument/" + id); return result; }
我们用类似的接口即可实现文件上传。接下来研究SpringMVC 是如何绑定参数的。
2. 绑定参数解读
SpringMVC 处理入口: org.springframework.web.servlet.DispatcherServlet#doDispatch:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == 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 (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } 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); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
1. org.springframework.web.servlet.DispatcherServlet#checkMultipart 检查是否是上传文件的接口,如果是上传文件的接口就重置processedRequest 的引用
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + "this typically results from an additional MultipartFilter in web.xml"); } else if (hasMultipartException(request) ) { logger.debug("Multipart resolution failed for current request before - " + "skipping re-resolution for undisturbed error rendering"); } else { try { 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; }
1. multipartResolver 是 org.springframework.web.multipart.support.StandardServletMultipartResolver。org.springframework.web.multipart.support.StandardServletMultipartResolver#isMultipart: 判断是否是文件上传的请求。
public boolean isMultipart(HttpServletRequest request) { // Same check as in Commons FileUpload... if (!"post".equals(request.getMethod().toLowerCase())) { return false; } String contentType = request.getContentType(); return (contentType != null && contentType.toLowerCase().startsWith("multipart/")); }
2. 最后调用org.springframework.web.multipart.support.StandardServletMultipartResolver#resolveMultipart 解析Multipart
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); }
可以看出是创建了一个StandardMultipartHttpServletRequest 对象并且返回去替换掉原来request的引用。org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#StandardMultipartHttpServletRequest(javax.servlet.http.HttpServletRequest, boolean) 构造如下
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); if (!lazyParsing) { parseRequest(request); } }
然后调用: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest
private void parseRequest(HttpServletRequest request) { try { // 拿请求接收的文件, 也就是tomcat 临时接收到的文件。 Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<String>(parts.size()); MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size()); for (Part part : parts) { // 拿到的信息样式: form-data; name="imgFile"; filename="RegexUtils.java" String disposition = part.getHeader(CONTENT_DISPOSITION); String filename = extractFilename(disposition); if (filename == null) { filename = extractFilenameWithCharset(disposition); } if (filename != null) { files.add(part.getName(), new StandardMultipartFile(part, filename)); } else { this.multipartParameterNames.add(part.getName()); } } setMultipartFiles(files); } catch (Throwable ex) { throw new MultipartException("Could not parse multipart servlet request", ex); } }
(1) 从request.getParts() 获取tomcat 接收到的临时文件
(2) files.add(part.getName(), new StandardMultipartFile(part, filename)); 创建一个StandardMultipartFile
(3) org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest#setMultipartFiles 缓存起来。方法如下:
protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) { this.multipartFiles = new LinkedMultiValueMap<String, MultipartFile>(Collections.unmodifiableMap(multipartFiles)); }
经过上面过程之后的processedRequest 如下:(对上传文件的请求提取了文件信息并且记录起来)
2. ha.handle(processedRequest, response, mappedHandler.getHandler()); 处理以及装配文件参数到controller
1. 会调用到:org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; }
2. org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues 解析参数
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); } throw ex; } } if (args[i] == null) { throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); } } return args; }
3. 最后调用到org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument 解析参数
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]"); } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
(1) 首先调用 org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver 获取参数解析器
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) { if (logger.isTraceEnabled()) { logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + parameter.getGenericParameterType() + "]"); } if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
这里获取到的参数解析器是: org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
(2) 然后调用org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument 解析参数
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); Object resolvedName = resolveStringValue(namedValueInfo.name); if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); try { arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); } catch (ConversionNotSupportedException ex) { throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } catch (TypeMismatchException ex) { throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
1》 resolveStringValue(namedValueInfo.name); 获取到参数的名称, 这里获取到的值是: imgFile
2》 调用org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName 获取参数的值
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } Object arg = null; if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0) : files); } } if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; }
上面的代码会走return mpArg; 然后结束方法。所以解析的过程是在:org.springframework.web.multipart.support.MultipartResolutionDelegate#resolveMultipartArgument
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 (multipartRequest == null && isMultipart) { multipartRequest = adaptToMultipartHttpServletRequest(request); } return (multipartRequest != null ? multipartRequest.getFile(name) : null); } else if (isMultipartFileCollection(parameter)) { if (multipartRequest == null && isMultipart) { multipartRequest = adaptToMultipartHttpServletRequest(request); } return (multipartRequest != null ? multipartRequest.getFiles(name) : null); } else if (isMultipartFileArray(parameter)) { if (multipartRequest == null && isMultipart) { multipartRequest = adaptToMultipartHttpServletRequest(request); } if (multipartRequest != null) { List<MultipartFile> multipartFiles = multipartRequest.getFiles(name); return multipartFiles.toArray(new MultipartFile[multipartFiles.size()]); } else { return null; } } else if (servletPartClass != null) { if (servletPartClass == parameter.getNestedParameterType()) { return (isMultipart ? RequestPartResolver.resolvePart(request, name) : null); } else if (isPartCollection(parameter)) { return (isMultipart ? RequestPartResolver.resolvePartList(request, name) : null); } else if (isPartArray(parameter)) { return (isMultipart ? RequestPartResolver.resolvePartArray(request, name) : null); } } return UNRESOLVABLE; }
上面代码会走 multipartRequest.getFile(name) 获取MultipartFile, 最后交给:org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest#getFile (实际上也就是拿取上面checkMultipart 过程中最后一步缓存起来的信息。获取到的实际类型是org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile)
public MultipartFile getFile(String name) { return getMultipartFiles().getFirst(name); } protected MultiValueMap<String, MultipartFile> getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } return this.multipartFiles; }
最后映射到参数上的MultipartFile 如下:
3. multipartFile.transferTo(new File(fileDir + fileName));// 保存文件
实际走的方法是:org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile#transferTo
public void transferTo(File dest) throws IOException, IllegalStateException { this.part.write(dest.getPath()); }
接着调用: org.apache.catalina.core.ApplicationPart#write
public void write(String fileName) throws IOException { File file = new File(fileName); if (!file.isAbsolute()) { file = new File(location, fileName); } try { fileItem.write(file); } catch (Exception e) { throw new IOException(e); } }
接着调用: org.apache.tomcat.util.http.fileupload.disk.DiskFileItem#write
public void write(File file) throws Exception { if (isInMemory()) { FileOutputStream fout = null; try { fout = new FileOutputStream(file); fout.write(get()); fout.close(); } finally { IOUtils.closeQuietly(fout); } } else { File outputFile = getStoreLocation(); if (outputFile != null) { // Save the length of the file size = outputFile.length(); /* * The uploaded file is being stored on disk * in a temporary location so move it to the * desired file. */ if (!outputFile.renameTo(file)) { BufferedInputStream in = null; BufferedOutputStream out = null; try { in = new BufferedInputStream( new FileInputStream(outputFile)); out = new BufferedOutputStream( new FileOutputStream(file)); IOUtils.copy(in, out); out.close(); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } } } else { /* * For whatever reason we cannot write the * file to disk. */ throw new FileUploadException( "Cannot write uploaded file to disk!"); } } }
getStoreLocation() 获取到文件, 然后进行拷贝操作,拷贝到指定的目录。