//请求进来
DispatcherServlet.doDispatch()1032行
processedRequest = checkMultipart(request);1044行
return this.multipartResolver.resolveMultipart(request);1210行
StandardMultipartHttpServletRequest.parseRequest()93行
Collection<Part> parts = request.getParts();95行
Request.parseParts()2750行
List<FileItem> items = upload.parseRequest(new ServletRequestContext(this));2834行
FileUploadBase.parseRequest()296行
Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);317行
Streams.copy()89行
public static long copy(final InputStream inputStream,
final OutputStream outputStream, final boolean closeOutputStream,
final byte[] buffer)
throws IOException {
try (OutputStream out = outputStream;
InputStream in = inputStream) {
long total = 0;
for (;;) {
final int res = in.read(buffer);
if (res == -1) {
break;
}
if (res > 0) {
total += res;
if (out != null) {
out.write(buffer, 0, res);
}
}
}
if (out != null) {
if (closeOutputStream) {
out.close();
} else {
out.flush();
}
}
in.close();
return total;
}
}
结论:
1.在进入controller时,上传文件已通过Stream.copy存在服务器磁盘上或者内存里了,存储位置由fileSizeThreshold决定,文件大小达到该阈值,将写入临时目录,默认为0,即所有文件都会直接写入磁盘临时文件中;小于该阈值则放在内存里。判断逻辑就在Stream.copy中的out.write里。
2.看了下Stream.copy的代码,是用一个InputStream和一个OutputStream进行上传文件落盘的,通过文件流的方式,就不用等待文件全部上传完了以后再处理,也就是上传一点,然后就落盘一点,尽量减少内存空间的消耗。
这里有个疑问,麻烦大佬们帮忙看看理解的对不对:
文件是否在前端发起请求时就已经在上传了,因为看到FileUploadBase.parseRequest中有一步操作final FileItemIterator iter = getItemIterator(ctx)此调用的函数里生成了MultipartStream,这个类负责是从文件流中读取文件数据的,看起来上传文件的InputStream从请求到达服务器时就一直开着的,在代码各种操作的同时,文件其实也一直在上传。如果文件较小且上传较快,当开始Stream.copy时,文件其实已经全部到达内存里了。
3.在进入controller,业务逻辑中打开了MultipartFile的InputStream后,一定要记得关闭,不然框架自动清除已落盘文件会失败,因为流处于打开状态,删除程序无法操作该文件。
删除代码见DispatcherServlet.doDispatch中1107行。
参数配置:spring.servlet.multipart开头
(1)enabled:是否开启文件上传自动配置,默认开启。
(2)location:上传文件的临时目录。
(3)maxFileSize:最大文件大小,以字节为单位,默认为1M。
(4)maxRequestSize:整个请求的最大容量,默认为10M。
(5)fileSizeThreshold:文件大小达到该阈值,将写入临时目录,默认为0,即所有文件都会直接写入磁盘临时文件中。
(6)resolveLazily:是否惰性处理请求,默认为false。
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);//处理上传文件
}
}
1)当resolveLazily为true时,我们在业务处理逻辑中,直接调用StandardMultipartHttpServletRequest接口的getXxx()方法就可以获取表单参数或表单文件信息,因为此时已经解析完成。
2)当resolveLazily为false时,在MultipartResolver.resolveMultipart()阶段并不会进行文件请求解析。也就是说,此时StandardMultipartHttpServletRequest对象的成员变量都是空值。在即将进入到Controller时,初始化方法参数,发现有MultipartFile参数,此时才会去解析上传请求,即调用parseRequest方法。
InvocableHandlerMethod
/**
* Invoke the method after resolving its argument values in the context of the given request.
* <p>Argument values are commonly resolved through
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
* The {@code providedArgs} parameter however may supply argument values to be used directly,
* i.e. without argument resolution. Examples of provided argument values include a
* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
* Provided argument values are checked before argument resolvers.
* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
* resolved arguments.
* @param request the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
* @return the raw value returned by the invoked method
* @throws Exception raised if no suitable argument resolver can be found,
* or if the method raised an exception
* @see #getMethodArgumentValues
* @see #doInvoke
*/
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
AbstractMultipartHttpServletRequest
/**
* Obtain the MultipartFile Map for retrieval,
* lazily initializing it if necessary.
* @see #initializeMultipart()
*/
protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
if (this.multipartFiles == null) {
initializeMultipart();
}
return this.multipartFiles;
}
StandardMultipartHttpServletRequest
@Override
protected void initializeMultipart() {
parseRequest(getRequest());
}