一、页面表单
<form role="form" action="/upload" method="post" enctype="multipart/form-data">
<input type="email" name="email">
<input type="text" name="username">
<input type="file" name="headerImg" >
<!--多个文件上传加multiple-->
<input type="file" name="photos" multiple>
<button type="submit" >Submit</button>
</form>
二、文件上传代码
/**
* MultipartFile 自动封装上传过来的文件
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerImg={},photos={}",
email,username,headerImg.getSize(),photos.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "main";
}
配置文件修改:
#文件上传的相关配置,第一个是单个文件最大大小,第二个是总的文件最大大小
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
三、自动配置原理
文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties配置属性类
//MultipartAutoConfiguration类中
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
自动配置好了 StandardServletMultipartResolver 【文件上传解析器(判断是否是文件请求)】
文件上传原理:
1、请求进来 使用文件上传解析器判断(checkMultipart中的isMultipart方法判断)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
//doDispatch方法中,先判断是否是文件请求
processedRequest = this.checkMultipart(request);
//通过请求是否被包装,确定是否是文件上传请求
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
文件上传解析器multipartResolver只有一个StandardServletMultipartResolver :
checkMultipart的具体代码:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
//判断是否是文件请求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
} else if (this.hasMultipartException(request)) {
this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
} else {
try {
//封装文件请求,变为MultipartHttpServletRequest请求类型
return this.multipartResolver.resolveMultipart(request);
} catch (MultipartException var3) {
if (request.getAttribute("javax.servlet.error.exception") == null) {
throw var3;
}
}
this.logger.debug("Multipart resolution failed for error dispatch", var3);
}
}
this.multipartResolver.isMultipart(request):判断是否是文件请求
return this.multipartResolver.resolveMultipart(request);封装文件请求为请求类型MultipartHttpServletRequest,具体的是StandardMultipartHttpServletRequest。
2、参数解析器来解析请求中的文件内容封装成MultipartFile
最终选择RequestPartMethodArgumentResolver参数解析器解析,
RequestPartMethodArgumentResolver调用resolveArgument方法,如下是方法中的关键代码:
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
而上面这个resolveMultipartArgument方法关键代码如下:
//如果参数是文件数组,从请求的Map中 拿取请求的文件 赋给请求参数
parts = ((MultipartHttpServletRequest)multipartRequest).getFiles(name);
3、StandardServletMultipartResolver 将request中的文件信息封装为一个Map。
StandardServletMultipartResolver 将普通request包装为MultipartHttpServletRequest(接口),更具体的是StandardMultipartHttpServletRequest类
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
StandardMultipartHttpServletRequest的parseRequest方法中将request中文件信息封装为一个Map,上述的getFiles方法就是从Map中拿取对应的文件
//AbstractMultipartHttpServletRequest类
public List<MultipartFile> getFiles(String name) {
List<MultipartFile> multipartFiles = (List)this.getMultipartFiles().get(name);
return multipartFiles != null ? multipartFiles : Collections.emptyList();
}
getMultipartFiles()会返回一个Map,MultiValueMap<String, MultipartFile>
protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
if (this.multipartFiles == null) {
this.initializeMultipart();
}
return this.multipartFiles;
}
//StandardMultipartHttpServletRequest类 上面代码的initializeMultipart具体实现
private void parseRequest(HttpServletRequest request) {
try {
Collection<Part> parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet(parts.size());
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap(parts.size());
Iterator var4 = parts.iterator();
while(var4.hasNext()) {
Part part = (Part)var4.next();
String headerValue = part.getHeader("Content-Disposition");
ContentDisposition disposition = ContentDisposition.parse(headerValue);
String filename = disposition.getFilename();
if (filename != null) {
if (filename.startsWith("=?") && filename.endsWith("?=")) {
filename = StandardMultipartHttpServletRequest.MimeDelegate.decode(filename);
}
files.add(part.getName(), new StandardMultipartHttpServletRequest.StandardMultipartFile(part, filename));
} else {
this.multipartParameterNames.add(part.getName());
}
}
this.setMultipartFiles(files);
} catch (Throwable var9) {
this.handleParseFailure(var9);
}
FileCopyUtils,实现文件流的拷贝