文件上传原理
# 400.文件上传原理
本文就来讲讲文件上传的原理
我们从两处着手:
- 是否做了自动配置,有的话配置了什么;
- 调试源码,看下功能怎么实现
# MultipartAutoConfiguration
我们来看看自动配置类:
@Configuration(proxyBeanMethods = false)
@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 {
private final MultipartProperties multipartProperties;
public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}
@Bean
@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
可以看到配置了 MultipartConfigElement
,这是一些配置信息;
还有配置了 StandardServletMultipartResolver
,文件上传解析器。这是默认的,也就是通过 Servlet 来上传文件的;如果想用其他方式上传,则需要自定义文件上传解析器。
# 以 debug 方式启动
接下来我们重启项目,并先忽略所有断点,等到了上传文件,点击提交前,再打开断点。
在 doDispatch
方法中,会先判断是否为文件上传请求:
怎么判断的呢?就是用 multipartResolver
,文件上传解析器:
其实底层就是看请求,是否以 multipart
开头:
@Override
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
2
3
4
接下来,就是解析该文件上传的请求:
然后是将其封装为一个 MultipartHttpServletRequest
对象:
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
2
3
4
回到 doDispatch,此时就会记录下,该请求是文件上传请求:
然后继续步入 handleInternal
方法:
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
2
3
4
5
然后在步入 invokeHandlerMethod
方法:
这里就会调用参数解析器的方法,来解析参数,例如我们的 @RequestPart
注解:
然后我们步入到 invokeAndHandle
方法:
该方法就会封装参数,并执行我们写的目标方法:
我们步入到 invokeForRequest
方法:
@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);
}
2
3
4
5
6
7
8
9
再步入 getMethodArgumentValues
方法:该方法就是遍历所有的参数解析器,看其是否支持该种类型:
如果支持,则会调用 resolveArgument
方法去解析参数:步入进去
resolveArgument
方法则会调用解析器的方法,我们再步入进去:
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
2
3
4
5
6
7
8
9
10
11
在 RequestPartMethodArgumentResolver
中,首先会获取注解的一些信息,然后调用代理对象去解析,我们步入 resolveMultipartArgument
方法:
所以其底层原理,就是将 request 中文件的信息,封装为 multipart
:
# MultipartFile
源码如下:
public interface MultipartFile extends InputStreamSource {
String getName();
@Nullable
String getOriginalFilename();
@Nullable
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
@Override
InputStream getInputStream() throws IOException;
default Resource getResource() {
return new MultipartFileResource(this);
}
void transferTo(File dest) throws IOException, IllegalStateException;
default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
可以看到 transferTo
方法,其底层就是调用文件复制工具类的方法,来完成文件复制的;