Spring 文件上传使用的总结及MultipartFile的解析
文件上传
前后台交互
使用MultipartFile参数接收:
单文件上传
前端<form>
标签
1 2 3 4
| <form action="/UploadOneServlet.do" method="post" name="f_upload" enctype="multipart/form-data"> <input type="file" name="multipartFile" /> <input type="submit" value="上传" /> </form>
|
在<form>
标签中添加enctype="multipart/form-data"
,即可实现multipart
类型数据上传。
后台接口
1 2 3 4 5
| @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST) @ResponseBody public String upload(@RequestParam("multipartFile") MultipartFile multipartFile) { }
|
后端中的对应方法中使用MultipartFile
类型参数进行接收。必须添加@RequestParam("multipartFile")
参数,否则报错。@RequestParam
的value
与前端imput
标签中的name
对应。
- 前端
<form>
标签1 2 3 4
| <form action="/UploadOneServlet.do" method="post" name="f_upload" enctype="multipart/form-data"> <input type="file" name="multipartFiles" multiple/> <input type="submit" value="上传" /> </form>
|
在<input>
标签中添加multiple
参数即可在一个框中选择多个文件上传
- 后台接口
1 2 3 4 5
| @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST) @ResponseBody public String upload(@RequestParam("multipartFile") List<MultipartFile> multipartFile) { }
|
后台使用List
,即可前台上传的多个文件。如果使用单个MultipartFile
类型进行接收则只能接收一个文件。
- 前端
<form>
标签1 2 3 4 5 6
| <form action="/UploadOneServlet.do" method="post" name="f_upload" enctype="multipart/form-data"> <input type="file" name="multipartFiles" /> <input type="file" name="multipartFiles" /> <input type="file" name="multipartFiles" /> <input type="submit" value="上传" /> </form>
|
前台的多个<input>
标签的name
相同
- 后台接口
1 2 3 4
| @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST) @ResponseBody public String upload(@RequestParam("multipartFiles") List<MultipartFile> multipartFiles) { }
|
后台接收方法与一个input标签上传多个文件时相同,使用List<MultipartFile>
进行接收。@RequestParam
的value
与前端imput
标签中的name
对应。
混合使用
前端<form>
标签
1 2 3 4 5
| <form action="/UploadOneServlet.do" method="post" name="f_upload" enctype="multipart/form-data"> <input type="file" name="multipartFiles" /> <input type="file" name="multipartFile" multiple/> <input type="submit" value="上传" /> </form>
|
前台使用name
不同的<input>
标签上传文件
后台接口
1 2 3 4
| @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST) @ResponseBody public String upload(@RequestParam("multipartFile") List<MultipartFile> multipartFiles, @RequestParam("multipartFiles") MultipartFile multipartFile) { }
|
后台使用一个List<MultiPartFile>
类型和MultiPartFile
类型进行接收。@RequestParam
的value
与前端imput
标签中的name
一一对应。
使用HttpServletRequest接收
- 前端
<input>
标签1 2 3 4 5
| <form action="/UploadOneServlet.do" method="post" name="f_upload" enctype="multipart/form-data"> <input type="file" name="multipartFiles" /> <input type="file" name="multipartFile" multiple/> <input type="submit" value="上传" /> </form>
|
- 后台接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST) @ResponseBody public String upload(HttpServletRequest request){ List<MultipartFile> files = new ArrayList<>(); MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request; Iterator<String> a = multipartHttpServletRequest.getFileNames(); while (a.hasNext()) { String name = a.next(); List<MultipartFile> multipartFiles = multipartHttpServletRequest.getFiles(name); files.addAll(multipartFiles); } upload(files); return "success"; }
|
文件上传
之前的几种情况已经接收到了前台上传的List<MultipartFile>
文件集合,遍历集合实现文件上传
1 2 3 4 5 6 7 8 9
| private void upload(List<MultipartFile> multipartFiles) throws Exception{ for (MultipartFile multipartFile : multipartFiles) { String fileName = multipartFile.getOriginalFilename(); String filePath = ContextLoader.getCurrentWebApplicationContext().getServletContext().getRealPath("/") + "fileUpload"; String fileTotalName = filePath + File.separator + fileName; File f = new File(fileTotalName); multipartFile.transferTo(f); } }
|
MultiPartFile类解析
Spring MVC
中要使用MultipartFile
进行文件上传,需要先注入MultipartResolver
类型的Bean:
1 2 3 4 5 6 7 8
| <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize"> <value>52428800</value> </property> </bean>
|
之前解析DispatcherServlet
类时得知所有最终会调用这个类中的doDispatch
方法对request进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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);
mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } } 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()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
|
可以看到processedRequest = checkMultipart(request)
对reuqest进行判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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); } else { throw ex; } } } } return request; }
|
通过以上方法可以看到以下逻辑:
- 判断用户是否注入了
MultipartResolver
类型的Bean,如果注入了则使用它去检测当前请求是否为文件上传请求
- 如果为文件上传请求,就通过
MultipartResolver
去处理当前请求。
- 返回处理后的请求或为处理的请求。
文件上传请求判断
接下来看看Spring
是如何判断当前请求是文件上传请求的, 这里主要看CommonsMultipartResolver
:
1 2 3
| public boolean isMultipart(HttpServletRequest request) { return (request != null && ServletFileUpload.isMultipartContent(request)); }
|
这里直接使用了Apache
中的ServletFileUpload
:
1 2 3 4 5 6 7
| public static final boolean isMultipartContent( HttpServletRequest request) { if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { return false; } return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); }
|
这里可以看到request请求必须是POST类型,如果是,则用FileUploadBase
进行进一步判断:
1 2 3 4 5 6 7 8 9 10
| public static final boolean isMultipartContent(RequestContext ctx) { String contentType = ctx.getContentType(); if (contentType == null) { return false; } if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) { return true; } return false; }
|
以上方法说明:
只有当当前请求的contentType
是以”multipart/“开头的时候,才会将此请求当做文件上传的请求。
综上,Spring
判断当前请求是否是文件上传请求主要有两个条件:
- 当前请求为POST请求
- 当前请求的
contextType
必须以”multipart/“开头
这正符合了前端<form>
标签中的设置。
文件上传请求解析
如果是文件上传请求,则会调用multipartResolver.resolveMultipart(request)
对请求进行解析:
1 2 3 4 5
| public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); }
|
可以看到任然是调用parseRequest方法对request进行解析,并将结果存入MultipartParsingResult
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) { MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<String, MultipartFile>(); Map<String, String[]> multipartParameters = new HashMap<String, String[]>(); Map<String, String> multipartParameterContentTypes = new HashMap<String, String>();
for (FileItem fileItem : fileItems) { if (fileItem.isFormField()) { } else { CommonsMultipartFile file = createMultipartFile(fileItem); multipartFiles.add(file.getName(), file); if (logger.isDebugEnabled()) { logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() + " bytes with original filename [" + file.getOriginalFilename() + "], stored " + file.getStorageDescription()); } } } return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes); }
|
初始化了MultipartParsingResult
对象之后就能根据该对象去初始化MultipartHttpServletRequest
对象,供接口中调用。