1、文件上传
1.1 前端页面
使用表单进行提交,提交的类型为multipart/form-data
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<!-- multiple支持多文件上传 -->
<input type="file" name="photos" multiple>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
1.2 后端controller接收
RequestPart标注接收文件提交的名称,MultipartFile接收文件
@PostMapping("/upload")
public String upload(@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
System.out.println(headerImg.getSize());
System.out.println(photos.length);
headerImg.transferTo(new File("E://aaaa.png"));//将文件输出到指定位置
return null;
}
2 文件上传原理
2.1 文件解析器自动配置
springBoot启动时,会将容器中自动注入StandardServletMultipartResolver文件解析器
@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 = {"multipartResolver"}
)
@ConditionalOnMissingBean({MultipartResolver.class})
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
可通过修改MultipartProperties配置,对文件进行配置
@ConfigurationProperties(
prefix = "spring.servlet.multipart",
ignoreUnknownFields = false
)
public class MultipartProperties {
private boolean enabled = true;
private String location;
private DataSize maxFileSize = DataSize.ofMegabytes(1L);// 单个数据的大
private DataSize maxRequestSize = DataSize.ofMegabytes(10L);//总数据的大小
private DataSize fileSizeThreshold = DataSize.ofBytes(0L);
private boolean resolveLazily = false;
}
2.2 文件上传自动解析流程
2.2.1 将request封装成MultipartHttpServletRequest
请求进来使用文件上传解析器判断(isMultipart),并使用resolveMultipart方法将请求封装成MultipartHttpServletRequest并返回。
//DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//..............................
processedRequest = checkMultipart(request);
//............................
}
//DispatcherServlet
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
//....................
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
//....................
return this.multipartResolver.resolveMultipart(request);
//....................
}
}
//StandardServletMultipartResolver
//所以请求类型必须为multipart
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
//StandardServletMultipartResolver
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
2.2.2 使用参数解析器解析参数
文件上传参数MultipartFile的参数解析器为RequestPartMethodArgumentResolver
2.2.2.1 判断是否支持
标注了RequestPart注解,或者是MultipartFile类型或者是Part类型
//RequestPartMethodArgumentResolver
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return true;
}
else {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
//MultipartResolutionDelegate
public static boolean isMultipartArgument(MethodParameter parameter) {
Class<?> paramType = parameter.getNestedParameterType();
return MultipartFile.class == paramType || isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) || Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter);
}
2.2.2.2 对参数进行解析逻辑
//MultipartResolutionDelegate
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//...................
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest)
//..................
}
//MultipartResolutionDelegate
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request) throws Exception {
//获取MultipartHttpServletRequest,并将文集流封装成MultipartFile
//调用本地方法
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
//.........................
}
//根据名称生成的数组multipartFiles中获取对应的MultipartFile
public List<MultipartFile> getFiles(String name) {
List<MultipartFile> multipartFiles = (List)this.getMultipartFiles().get(name);
return multipartFiles != null ? multipartFiles : Collections.emptyList();
}