Spring 文件上传使用的总结及MultipartFile的解析
文件上传
前后台交互
使用MultipartFile参数接收:
单文件上传
- 前端- <form>标签
 | 12
 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类型数据上传。
 
- 后台接口 | 12
 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>标签| 12
 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参数即可在一个框中选择多个文件上传
- 后台接口| 12
 3
 4
 5
 
 | @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST)@ResponseBody
 public String upload(@RequestParam("multipartFile") List<MultipartFile> multipartFile) {
 
 }
 
 |  
 
List,即可前台上传的多个文件。如果使用单个MultipartFile类型进行接收则只能接收一个文件。
- 前端<form>标签| 12
 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相同
- 后台接口| 12
 3
 4
 
 | @RequestMapping(value = "/UploadOneServlet.do", method = RequestMethod.POST)@ResponseBody
 public String upload(@RequestParam("multipartFiles") List<MultipartFile> multipartFiles) {
 }
 
 |  
 
List<MultipartFile>进行接收。@RequestParam的value与前端imput标签中的name对应。
混合使用
- 前端- <form>标签
 | 12
 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>标签上传文件
 
- 后台接口 | 12
 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>标签| 12
 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>
 
 |  
 
- 后台接口| 12
 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>文件集合,遍历集合实现文件上传
| 12
 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:
| 12
 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进行处理:
| 12
 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进行判断:
| 12
 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:
| 12
 3
 
 | public boolean isMultipart(HttpServletRequest request) {return (request != null && ServletFileUpload.isMultipartContent(request));
 }
 
 | 
这里直接使用了Apache中的ServletFileUpload:
| 12
 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进行进一步判断:
| 12
 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)对请求进行解析:
| 12
 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:
| 12
 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);
 }
 }
 
 | 
| 12
 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对象,供接口中调用。