概述
目录
spring启动时的装配过程
StandardServletMultipartResolver
启动时DispatcherServlet被装载进Tomcat
文件request请求的处理过程
磁盘缓存临时文件的创建
Form-data里不是文件的入参被添加进Request的Parameters集合里
缓存文件的删除
(62条消息) MultipartFile文件上传 (1) - Controller同时处理文件流和附加入参示例-CSDN博客https://blog.csdn.net/noob_can/article/details/124093033?spm=1001.2014.3001.5502
SpringBoot内嵌Tomcat(3)- 【组件结构及初始化】源码简析https://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)
上图中执行的方法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的后半部分:
- 从底层的org.apache.coyote.Request#getParameters里拿到了存非文件流参数的容器'parameters' ! (此时demo里可以看到parameters还没有参数值对)
- 在本示例配置时未指定临时文件缓存地址“location” , 所以这里替换成了从ServletContext拿到的 "javax.servlet.context.tempdir"
接下来实例化了:
- 磁盘上的文件缓存仓库: org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory ;
- 文件上传处理类 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是否为空 ”, 以此来区分入参是文件类型。
要注意的是: 示例中真正上传的文件只有2个,对应下图里parts[0] 和 parts[2] 。
'isFormField' 表示: 入参是否是一个简单属性
Form-data里不是文件的入参被添加进Request的Parameters集合里
FileItem最终被解析转换为:org.apache.catalina.core.ApplicationPart 。对于简单属性入参,则被添加到Request的Parameters集合里!
所以这也是为什么: @RequestParam注解对应的入参解析器RequestParamMethodArgumentResolver(底层是Request#getParameter | #getParameterValues)能真正拿到入参值的原因!
回到StandardMultipartHttpServletRequest#parseRequest 对 ApplicationPart进行分类保存:也是通过filename不为空则标识入参是文件!
如果Controller的接口方法入参是没有注解的默认复杂对象类型,那么在ModelAttributeMethodProcessor#resolveArgument解析入参过程中,将创建该入参对象实例,并绑定request里对应key的入参值。
缓存文件的删除
在处理该类型请求结束时:
- 首先 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 (这里是删除所有类型的缓存)
最后
以上就是可爱白云为你收集整理的MultipartFile文件上传 (2) - DispatcherServlet#doDispatch 如何处理Multipart请求启动时的装配过程 启动时DispatcherServlet被装载进Tomcatrequest请求的处理过程的全部内容,希望文章能够帮你解决MultipartFile文件上传 (2) - DispatcherServlet#doDispatch 如何处理Multipart请求启动时的装配过程 启动时DispatcherServlet被装载进Tomcatrequest请求的处理过程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复