File upload this scene actually talked with you many times before, this time because for SpringMVC source code analysis recently, so again pull this topic out “whipping corpse”, but the loose elder brother wants to talk about this topic from the perspective of the source code.

The premise of understanding the source code is to be able to use, so we still look at the usage first, and then to analyze the source code.

1. Two file parsing schemes

There are currently two different parsing solutions in SpringMVC for file upload requests:

  • StandardServletMultipartResolver
  • CommonsMultipartResolver

StandardServletMultipartResolver support Servlet3.0 standard file upload solution, using a very simple; CommonsMultiPartResolver needs to be used in conjunction with the Apache Commons FileUpload component, which is compatible with lower versions of servlets.

StandardServletMultipartResolver

To review the first StandardServletMultipartResolver usage.

Use StandardServletMultipartResolver, can be directly obtained on it own getPart upload file and save, this is a standard way of operating, this way also need not add any additional dependencies, Just make sure that the Servlet version is above 3.0.

First we need to configure multipart-config for the Servlet, which is responsible for handling the uploaded files. In SpringMVC, our requests are distributed through the DispatcherServlet, so we configure multipart-config for the DispatcherServlet.

The configuration is as follows:

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</serv
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
    <multipart-config>
        <location>/tmp</location>
        <max-file-size>1024</max-file-size>
        <max-request-size>10240</max-request-size>
    </multipart-config>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Then in the configuration file for SpringMVC provide a StandardServletMultipartResolver instance, pay attention to the instance id must be multipartResolver (see specific reasons: SpringMVC initialization process analysis).

<bean class="org.springframework.web.multipart.support.StandardServletMultipartResolver" id="multipartResolver">
</bean>

After the configuration is complete, we can develop a file upload interface like this:

@RestController public class FileUploadController { SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd/"); @PostMapping("/upload") public String fileUpload(MultipartFile file, HttpServletRequest req) { String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (! folder.exists()) { folder.mkdirs(); } String oldName = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); try { file.transferTo(new File(folder, newName)); return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName; } catch (IOException e) { e.printStackTrace(); } return "error"; } @PostMapping("/upload2") public String fileUpload2(HttpServletRequest req) throws IOException, ServletException { StandardServletMultipartResolver resolver = new StandardServletMultipartResolver(); MultipartFile file = resolver.resolveMultipart(req).getFile("file"); String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (! folder.exists()) { folder.mkdirs(); } String oldName = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); try { file.transferTo(new File(folder, newName)); return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName; } catch (IOException e) { e.printStackTrace(); } return "error"; } @PostMapping("/upload3") public String fileUpload3(HttpServletRequest req) throws IOException, ServletException { String other_param = req.getParameter("other_param"); System.out.println("other_param = " + other_param); String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (! folder.exists()) { folder.mkdirs(); } Part filePart = req.getPart("file"); String oldName = filePart.getSubmittedFileName(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); try { filePart.write(realPath + newName); return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName; } catch (IOException e) { e.printStackTrace(); } return "error"; }}

I here provides a total of three file upload interface, finally actually by StandardServletMultipartResolver for processing.

  1. The first interface is a file upload method that we usually use in the SpringMVC framework, which simply writes multipartFile in the parameter. The multipartFile is parsed from the current request. The concrete is responsible for parameter parsing is RequestParamMethodArgumentResolver.
  2. The second interface is actually an ancient implementation of file uploading with a plain HttpServletRequest parameter, and inside the parameter, We’ll manually using StandardServletMultipartResolver instance parsing (this kind of situation can need not his new a StandardServletMultipartResolver instance, Just inject it directly from the Spring container.
  3. The third interface we use the Servlet3.0 API, call GetPart to get the file, and then call the object’s write method to write the file can be.

Roughly a look, feel way is quite a lot, in fact, look carefully, all change from its, after we read the source code, I believe that the partners can also change out of more writing.

CommonsMultipartResolver

CommonsMultiPartResolver is probably familiar to many people. It’s compatible, but a bit outdated. Using CommonsMultiPartResolver requires that we first introduce the commons-fileUpload dependency:

< the dependency > < groupId > Commons fileupload - < / groupId > < artifactId > Commons fileupload - < / artifactId > < version > 1.4 < / version > </dependency>

Then provide an instance of CommonsMultiPartResolver in the SpringMVC configuration file as follows:

<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">-->
</bean>

The next step is to develop the file upload interface:

@PostMapping("/upload") public String fileUpload(MultipartFile file, HttpServletRequest req) { String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (! folder.exists()) { folder.mkdirs(); } String oldName = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); try { file.transferTo(new File(folder, newName)); return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName; } catch (IOException e) { e.printStackTrace(); } return "error"; }

This one is not easy to say.

File upload this song brother before in the video has also shared with you, the public number background reply SSM can view the video details.

Now that we’ve got the usage, let’s look at the principle.

2.StandardServletMultipartResolver

No nonsense, just look at the source code:

public class StandardServletMultipartResolver implements MultipartResolver { private boolean resolveLazily = false; public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } @Override public boolean isMultipart(HttpServletRequest request) { 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()) { try { for (Part part : request.getParts()) { if (request.getFile(part.getName()) ! = null) { part.delete(); } } } catch (Throwable ex) { } } } }

There are only four methods, one of which is a set method. Let’s look at the other three functional methods:

  1. IsMultipart: This method is used to determine whether the current request is a file upload request or not. This method is used to determine whether the current request is a file upload requestmultipart/If yes, it is a file upload request, otherwise it is not a file upload request.
  2. ResolveMultipart: this method is responsible for the current request encapsulates a StandardMultipartHttpServletRequest object and returns.
  3. CleanupMultipart: This method takes care of the cleanup, mainly cleaning up the cache.

In the process involves StandardMultipartHttpServletRequest object, we come to speak a little, too:

public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); if (! lazyParsing) { parseRequest(request); } } private void parseRequest(HttpServletRequest request) { try { Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); 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()); } } setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); }}

StandardMultipartHttpServletRequest object in the process of building, will automatically request parsing, call getParts method for all the items, and do judgment, the files and the general parameters respectively preserved for later use.

The logic here is simple.

3.CommonsMultipartResolver

Now look at the CommonsMultiPartResolver.

Let’s look at its isMultipart method first:

@Override public boolean isMultipart(HttpServletRequest request) { return ServletFileUpload.isMultipartContent(request);  } public static final boolean isMultipartContent( HttpServletRequest request) { if (! POST_METHOD.equalsIgnoreCase(request.getMethod())) { return false; } return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); }

ServletFileUpload. IsMultipartContent method are introduced in our Commons – fileupload package. Its judgment logic consists of two steps: first, it checks to see if a POST request is made, and then it checks to see if the Content-Type starts with multipart/.

Consider its resolvMultiPart method:

@Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { 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()); }}

According to resolveLazily attribute values, choose two different strategies to reconstruct the current object into a DefaultMultipartHttpServletRequest object. If resolveLazily is true, then the request parsing in initializeMultipart method, otherwise, first to build DefaultMultipartHttpServletRequest object.

The specific analytical method is as follows:

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) {
        //...
    }
}
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<>();
    for (FileItem fileItem : fileItems) {
        if (fileItem.isFormField()) {
            String value;
            String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
            try {
                value = fileItem.getString(partEncoding);
            }
            catch (UnsupportedEncodingException ex) {
                value = fileItem.getString();
            }
            String[] curParam = multipartParameters.get(fileItem.getFieldName());
            if (curParam == null) {
                multipartParameters.put(fileItem.getFieldName(), new String[] {value});
            }
            else {
                String[] newParam = StringUtils.addStringToArray(curParam, value);
                multipartParameters.put(fileItem.getFieldName(), newParam);
            }
            multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
        }
        else {
            CommonsMultipartFile file = createMultipartFile(fileItem);
            multipartFiles.add(file.getName(), file);
        }
    }
    return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}

The parsing here is to first get the FileItem collection and then call the parseFileItems method for further parsing. In further parsing, it will first determine whether this is a file or an ordinary parameter. If it is an ordinary parameter, it will be saved into multipartParameters. During the saving process, it will also determine whether it is an array. And then he will save ContentType to multipartParameterContentTypes parameters, files are saved to the multipartFiles, Finally, three Maps form a MultipartParsingResult object and return it.

At this point, StandardServletMultipartResolver and CommonsMultipartResolver source, and everyone had said, you can see, still relatively easy.

4. Analyze the process

Finally, let’s look at the parsing process again.

Take the following interface as an example (because in real life development you usually upload files as follows) :

@PostMapping("/upload") public String fileUpload(MultipartFile file, HttpServletRequest req) { String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (! folder.exists()) { folder.mkdirs(); } String oldName = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); try { file.transferTo(new File(folder, newName)); return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName; } catch (IOException e) { e.printStackTrace(); } return "error"; }

The multipartFile object is retrieved from the parameter parser. For the parameter parser, you can refer to: In-depth analysis for SpringMVC parameter parser, involves the parameters of the parser is RequestParamMethodArgumentResolver here.

In RequestParamMethodArgumentResolver# resolveName method has the following a line of code:

if (servletRequest ! = null) { Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg ! = MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; }}

This method will parse the request and return either a multipartFile object or an array of multipartFile objects.

@Nullable 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); } else 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 if (Part.class == parameter.getNestedParameterType()) { if (! isMultipart) { return null; } return request.getPart(name); } else if (isPartCollection(parameter)) { if (! isMultipart) { return null; } List<Part> parts = resolvePartList(request, name); return (! parts.isEmpty() ? parts : null); } else if (isPartArray(parameter)) { if (! isMultipart) { return null; } List<Part> parts = resolvePartList(request, name); return (! parts.isEmpty() ? parts.toArray(new Part[0]) : null); } else { return UNRESOLVABLE; }}

First get the MultiPartRequest object, and then get the file or file array from it. If we use StandardServletMultipartResolver do file upload, is here to get multipartRequest StandardMultipartHttpServletRequest; File upload, if we use CommonsMultipartResolver do get multipartRequest is DefaultMultipartHttpServletRequest here.

5. Summary

So far, we’ve analyzed seven of the nine SpringMVC components, with two to go, and we’re about to finish. After that, Songgo will post a PDF and share it with you.