In fact, the file upload this songgo before and we have talked about many times, this time because of the recent SpringMVC source analysis, so again pull this topic out “whip dead”, but this time Songgo want to talk about this topic from the source perspective.

The prerequisite for understanding source code is to be able to use it first, so let’s look at the usage first and then analyze the source code.

1. Two file parsing schemes

There are currently two different resolution schemes in SpringMVC for requests to upload files:

  • 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 older 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 the Servlet version is above 3.0.

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

The configuration mode 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>
Copy the code

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 article).

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

Once configured, we can develop a file upload interface as follows:

@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"; }}Copy the code

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

  1. The first interface is a file upload that we see in the SpringMVC framework, where we say MultipartFile, which is actually parsed from the current request, The concrete is responsible for parameter parsing is RequestParamMethodArgumentResolver.
  2. The second interface is actually an old file upload implementation, and the argument is just plain HttpServletRequest, and then in the argument, We’ll manually using StandardServletMultipartResolver instance parsing (this kind of situation can need not his new a StandardServletMultipartResolver instance, Just inject it into the Spring container.
  3. The third interface we use Servlet3.0 API, call getPart to get the file, and then call the object’s write method to write out the file.

Generally speaking, feel the way is quite much, in fact, look carefully, all changes from its zong, we will read the source code, I believe that partners can change more writing.

CommonsMultipartResolver

CommonsMultipartResolver is probably familiar to a lot of people, but it’s a bit out of date. Using CommonsMultipartResolver requires that we first introduce the Commons-Fileupload dependency:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
Copy the code

Then provide the CommonsMultipartResolver instance in the SpringMVC configuration file as follows:

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

Next, 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";
}
Copy the code

There’s nothing to say about this one. It’s easier.

File upload this song ge before in the video also shared with you, the public account background reply SSM can view the details of the video.

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

2.StandardServletMultipartResolver

No nonsense, directly to see 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(! (requestinstanceof AbstractMultipartHttpServletRequest) ||
				((AbstractMultipartHttpServletRequest) request).isResolved()) {
			try {
				for (Part part : request.getParts()) {
					if(request.getFile(part.getName()) ! =null) { part.delete(); }}}catch (Throwable ex) {
			}
		}
	}

}
Copy the code

Here are just four methods, one of which is set. 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. This method is used to determine whether the current request is a file upload requestmultipart/At first, if it is, 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 cleans up the mess, 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); }}Copy the code

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

Let’s look at CommonsMultipartResolver.

Let’s start with its isMultipart method:

@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));
}
Copy the code

ServletFileUpload. IsMultipartContent method are introduced in our Commons – fileupload package. Its logic is two-step: first check if it is a POST request, and then check if the Content-Type starts with a multipart/.

Look again at its resolveMultipart method:

@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
	if (this.resolveLazily) {
		return new DefaultMultipartHttpServletRequest(request) {
			@Override
			protected void initializeMultipart(a) { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); }}; }else {
		MultipartParsingResult parsingResult = parseRequest(request);
		return newDefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); }}Copy the code

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 analysis 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);
}
Copy the code

Parsing is done by first retrieving the FileItem collection and then calling 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 to the multipartParameters, and it will determine whether it is an array during the saving process. 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. Parse the process

Finally, let’s go through the parsing process again.

Take the following interface as an example (since this is how files are usually uploaded in real development) :

@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";
}
Copy the code

The MultipartFile object is obtained from the parameter parser. The MultipartFile object is obtained from the parameter parser. 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) {returnmpArg; }}Copy the code

This method resolves the request and returns either a MultipartFile object or an array of MultipartFile.

@Nullable
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
		throws Exception {
	MultipartHttpServletRequest multipartRequest =
			WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
	booleanisMultipart = (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 {
		returnUNRESOLVABLE; }}Copy the code

First get the multipartRequest object, and then get the files or arrays of files 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

Well, today and everyone simple analysis of the next two file upload component source, so far, SpringMVC nine components we have analyzed 7, there are two remaining, is about to end, after the end of songko will be organized into PDF share with you.