文件上传
页面设置表单标签
1、示例
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="files" multiple/><br/>
<input type="file" name="file"/><br/>
<input type="submit" value="上传"/>
</form>
2、enctype 属性:规定在将表单数据发送到服务器之前如何对其进行编码
3、只有 method="post" 时才使用 enctype 属性
值 | 描述 |
application/x-www-form-urlencoded | 默认。在发送前对所有字符进行编码(将空格转换为 "+" 符号,特殊字符转换为 ASCII HEX 值) |
multipart/form-data | 不对字符编码。当使用有文件上传控件的表单时,该值是必需的 |
text/plain | 将空格转换为 "+" 符号,但不编码特殊字符 |
4、encType="multipart/form-data" 表示提交的数据,以多段(每一个表单项一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器
5、multiple:多文件上传,默认为单文件上传
文件上传组件
1、组件(示例)
@Controller
public class FormUpload() {
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile file,
@RequestPart("files") MultipartFile[] files) throws IOException {
//保存文件
if(!file.isEmpty()){
String originalFilename = file.getOriginalFilename();
file.transferTo(new File("D:\\" + originalFilename));
}
if(files.length > 0){
for (MultipartFile file : files) {
if(!file.isEmpty()){
String originalFilename = file.getOriginalFilename();
file.transferTo(new File("D:\\" + originalFilename));
}
}
}
return "forwrad:/success";
}
}
2、MultipartFile:自动封装上传过来的文件
3、transferTo 方法:由 Spring Boot 的 FileCopyUtils 提供,实现文件流的拷贝
原理
1、文件上传自动配置类:MultipartAutoConfiguration
@AutoConfiguration
@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;
}
//自动配置StandardServletMultipartResolver(文件上传解析器);name="multipartResolver"
@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、步骤
(1)使用文件上传解析器,判断(isMultiparty)是否为文件上传请求,若是,则封装为(resolveMultipart,返回 MultipartHttpServletRequest)文件上传请求
(2)使用参数解析器(RequestPartMethodArgumentResolver),解析请求中的文件内容,将请求中文件信息封装为 MultiValueMap<String, MultipartFile>
3、文件上传解析器只有一个,默认条件解析器 @ConditionalOnMissingBean,按条件装配,即可以被自定义文件上传解析器覆盖
源码
1、DispactherServlet 类
(1)doDispacth 方法
//记录文件上传请求是否已被解析
boolean multipartRequestParsed = false;
//判断是否为文件上传请求
processedRequest = checkMultipart(request);
//processedRequest被封装为文件上传请求,与原生请求不同,则multipartRequestParsed为true
multipartRequestParsed = (processedRequest != request);
(2)checkMultipart 方法
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
//文件解析器不为null,且调用文件解析器的isMultipart方法判断是否为文件上传请求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
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 ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
2、Spring Boot 提供的默认文件上传解析器
public class StandardServletMultipartResolver implements MultipartResolver {
private boolean resolveLazily = false;
private boolean strictServletCompliance = false;
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
public void setStrictServletCompliance(boolean strictServletCompliance) {
this.strictServletCompliance = strictServletCompliance;
}
@Override
public boolean isMultipart(HttpServletRequest request) {
//使用String工具类,判断请求的请求类型是否为"multipart/form-data"或"multipart/"
return StringUtils.startsWithIgnoreCase(request.getContentType(),
(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
//封装为StandardMultipartHttpServletRequest(标准文件上传请求)
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
@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);
}
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战