MultipartFile文件上传 (2) - DispatcherServlet#doDispatch 如何处理Multipart请求

目录

spring启动时的装配过程

StandardServletMultipartResolver

 启动时DispatcherServlet被装载进Tomcat

文件request请求的处理过程

磁盘缓存临时文件的创建

Form-data里不是文件的入参被添加进Request的Parameters集合里

缓存文件的删除


(62条消息) MultipartFile文件上传 (1) - Controller同时处理文件流和附加入参示例-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/noob_can/article/details/124093033?spm=1001.2014.3001.5502

SpringBoot内嵌Tomcat(3)- 【组件结构及初始化】源码简析icon-default.png?t=N7T8https://my.oschina.net/u/3434392/blog/3213796

上文中,描述了 Tomcat的组件结构:

StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[].StandardWrapper[dispatcherServlet]

 对于整个文件上传请求的处理流程,要结合Tomcat各组件层级结构和它们的加载初始化过程来理解。

spring启动时的装配过程

application.yml

spring.servlet.multipart:
    max-file-size: 10MB # 文件的最大大小
    max-request-size: 50MB # 请求的最大大小
    file-size-threshold: 0  #  文件大小阈值,当大于这个阈值时将写入到磁盘,否则在内存中。 默认值为0

自动装配: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration

@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {

它实例化的3个对象:

  • MultipartProperties :它对上面3个参数有默认赋值。 @EnableConfigurationProperties

  • MultipartConfigElement :由MultipartProperties的参数创建而成,并在DispatcherServletAutoConfiguration里注入给创建的DispatcherServletRegistrationBean对象。

这里因为示例中没有配置“location” 属性, 所以这里初始值为空字符串。 最大文件字节、最大请求字节

  • StandardServletMultipartResolver :request请求的解析处理类,这里是创建的MultipartHttpServletRequest是StandardMultipartHttpServletRequest

StandardServletMultipartResolver

判定有文件流上传请求的依据: request的content-type是以‘multipart/’开头。

public class StandardServletMultipartResolver implements MultipartResolver {

	private boolean resolveLazily = false; // 在multipart类型请求进入时是否立即解析

	public void setResolveLazily(boolean resolveLazily) {
		this.resolveLazily = resolveLazily;
	}
	// 判定request的content-type 是 multipart
	public boolean isMultipart(HttpServletRequest request) {
		return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
	}
    // 封装原request为StandardMultipartHttpServletRequest, 它里面有解析这种请求的处理方法
	@Override
	public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
		return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
	}
    // 这里是清理缓存文件, 需要配合tomcat的Servlet处理文件流的过程来理解!
	@Override
	public void cleanupMultipart(MultipartHttpServletRequest request) {
		if (!(request instanceof AbstractMultipartHttpServletRequest) ||
				((AbstractMultipartHttpServletRequest) request).isResolved()) {
			// To be on the safe side: explicitly delete the parts, but only actual file parts (for Resin compatibility)
			try {
				for (Part part : request.getParts()) {
					if (request.getFile(part.getName()) != null) {
						part.delete();
					}
				}
			}
			catch (Throwable ex) {
				LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
			}
		}
	}
}

 启动时DispatcherServlet被装载进Tomcat

在TomcatWebServer启动时,装载DispatcherServlet,此时 DispatcherServletRegistrationBean#configure 被执行。

addRegistration:51, ServletRegistrationBean (org.springframework.boot.web.servlet)
register:108, DynamicRegistrationBean (org.springframework.boot.web.servlet)
onStartup:53, RegistrationBean (org.springframework.boot.web.servlet)
selfInitialize:230, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onStartup:-1, 1816073816 (org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext$$Lambda$430)
onStartup:53, TomcatStarter (org.springframework.boot.web.embedded.tomcat)
startInternal:5128, StandardContext (org.apache.catalina.core)
...
startInternal:841, StandardHost (org.apache.catalina.core)
...
initialize:123, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
<init>:104, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
getTomcatWebServer:437, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
getWebServer:191, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
createWebServer:178, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onRefresh:158, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:545, AbstractApplicationContext (org.springframework.context.support)
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:758, SpringApplication (org.springframework.boot)
refresh:750, SpringApplication (org.springframework.boot)
refreshContext:397, SpringApplication (org.springframework.boot)
run:315, SpringApplication (org.springframework.boot)
run:1237, SpringApplication (org.springframework.boot)
run:1226, SpringApplication (org.springframework.boot)
main:15, ApiGatewayApplication (com.noob)
org.springframework.boot.web.servlet.DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean

上图中执行的方法DynamicRegistrationBean#addRegistration :

在org.apache.catalina.core.ApplicationContext#addServlet (javax.servlet.ServletContext接口的实现) 方法里创建Servlet的包装类StandardWrapper,并链接到TomcatEmbeddedContext 。

返回了绑定了StandardWrapper和Context关系的ApplicationServletRegistration对象。

文件request请求的处理过程

 DispatcherServlet在第一次有请求访问时, 才会触发初始化#init过程!

initMultipartResolver:522, DispatcherServlet (org.springframework.web.servlet)
initStrategies:503, DispatcherServlet (org.springframework.web.servlet)
onRefresh:495, DispatcherServlet (org.springframework.web.servlet)
initWebApplicationContext:599, FrameworkServlet (org.springframework.web.servlet)
initServletBean:530, FrameworkServlet (org.springframework.web.servlet)
init:170, HttpServletBean (org.springframework.web.servlet)
init:158, GenericServlet (javax.servlet)
initServlet:1134, StandardWrapper (org.apache.catalina.core)
allocate:777, StandardWrapper (org.apache.catalina.core)
invoke:135, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
invoke:747, RemoteIpValve (org.apache.catalina.valves)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:373, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:868, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1589, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

 在DispatcherServlet#doDispatch的处理过程里: 一开头就会判定并解析StandardMultipartHttpServletRequest !

DispatcherServlet#checkMultipart (判定是Multipart类型)-> StandardServletMultipartResolver#resolveMultipart 将HttpServletRequest解析成 StandardMultipartHttpServletRequest:

         StandardMultipartHttpServletRequest#parseRequest -> org.apache.catalina.connector.Request#parseParts

 这里从StandardWrapper里拿到配置好的MultipartConfigElement就是在启动时给DispatcherServletRegistrationBean注入的!

Context里 allowCasualMultipartParsing 默认是false, 

如果在自定义配置了DispatcherServletRegistrationBean覆盖原Spring默认设置的情况下,不指定写入MultipartConfigElement将报错:

 

磁盘缓存临时文件的创建

接着来看Request#parseParts的后半部分:

  1. 从底层的org.apache.coyote.Request#getParameters里拿到了存非文件流参数的容器'parameters' ! (此时demo里可以看到parameters还没有参数值对)
  2. 在本示例配置时未指定临时文件缓存地址“location” , 所以这里替换成了从ServletContext拿到的 "javax.servlet.context.tempdir"

 接下来实例化了:

  1. 磁盘上的文件缓存仓库: org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory ; 
  2. 文件上传处理类  org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload,写入MultipartConfigElement的配置

 执行方法ServletFileUpload #parseRequest: 将文件内容写入到磁盘上的临时文件里,并以org.apache.tomcat.util.http.fileupload.FileItem来标识 。

不是文件类型的入参也会生成一个临时文件 字符串入参“name”对应的缓存文件内容就是它的传入值。

 磁盘上的临时文件。 这里不是文件入参也会有一个临时文件!!

创建FileItem的逻辑里: org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl#findNextItem():创建FileItemStreamImpl时会区分:“fileName是否为空 ”, 以此来区分入参是文件类型。

FileItemIteratorImpl#findNextItem

 要注意的是: 示例中真正上传的文件只有2个,对应下图里parts[0] 和 parts[2] 。

'isFormField' 表示: 入参是否是一个简单属性 

Form-data里不是文件的入参被添加进Request的Parameters集合里

FileItem最终被解析转换为:org.apache.catalina.core.ApplicationPart 。对于简单属性入参,则被添加到Request的Parameters集合里!

所以这也是为什么: @RequestParam注解对应的入参解析器RequestParamMethodArgumentResolver(底层是Request#getParameter | #getParameterValues)能真正拿到入参值的原因!

Request#parseParts

回到StandardMultipartHttpServletRequest#parseRequest 对 ApplicationPart进行分类保存:也是通过filename不为空则标识入参是文件!

StandardMultipartHttpServletRequest#parseRequest :按fileName有无来区分入参是否文件

如果Controller的接口方法入参是没有注解的默认复杂对象类型,那么在ModelAttributeMethodProcessor#resolveArgument解析入参过程中,将创建该入参对象实例,并绑定request里对应key的入参值。

ServletRequestDataBinder

缓存文件的删除

在处理该类型请求结束时:

  • 首先 DispatcherServlet#doDispatch -> #cleanupMultipart -> StandardServletMultipartResolver#cleanupMultipart : 遍历执行ApplicationPart#delete 删除文件入参的磁盘缓存。

  • 其次对于非文件入参的磁盘缓存的删除是在Tomcat层面:

org.apache.coyote.AbstractProcessorLight#process -> org.apache.coyote.http11.Http11Processor#service -> org.apache.catalina.connector#CoyoteAdapter.service : 它在筛选Servlet处理完请求之后, 需要回收请求资源org.apache.catalina.connector.Request#recycle (这里是删除所有类型的缓存)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值