文件上传

页面设置表单标签

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);
            }
        }
    }
}
posted @   半条咸鱼  阅读(291)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示