一、应用

1、构建文件上传表单

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">    <div class="form-group">        <label for="exampleInputEmail1">邮箱</label>        <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">    </div>    <div class="form-group">        <label for="exampleInputPassword1">名字</label>        <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">    </div>    <div class="form-group">        <label for="exampleInputFile">头像</label>        <input type="file" name="headerImg" id="exampleInputFile">        <p class="help-block">Example block-level help text here.</p>    </div>    <div class="form-group">        <label for="exampleInputFile">生活照</label>        <input type="file" name="photo" multiple>    </div>    <div class="checkbox">        <label>            <input type="checkbox"> Check me out        </label>    </div>    <button type="submit"  class="btn btn-primary">提交</button></form>

2、文件上传代码

//表单提交必须用post申请    @PostMapping("/upload")public String upload(@RequestParam("email") String email,                     @RequestParam("username") String username,                     @RequestPart("headerImg") MultipartFile headerImg, // MultipartFile:用于上传文件性能   @RequestPart:获取表单里的文件项                     @RequestPart("photos") MultipartFile[] photos) throws IOException {    log.info("上传的信息:email={},username={},headerImg={},photos={}",            email,username,headerImg.getSize(),photos.length);    if(!headerImg.isEmpty()){ //isEmpty,getOriginalFilename,transferTo:都是MultipartFile接口里的办法        String originalFilename = headerImg.getOriginalFilename();        headerImg.transferTo(new File("D:\\cache\\"+originalFilename));//transferTo:文件保留(传输)到哪    }    if(photos.length>0){        for (MultipartFile photo : photos) {            if(!photo.isEmpty()){                String originalFilename = photo.getOriginalFilename();                photo.transferTo(new File("D:\\cache\\"+originalFilename));            }        }    }    return "main";}}

如果上传呈现了超出限度的大小异样

org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field profile-photo exceeds its maximum permitted size of 1048576 bytes.

能够批改底层默认限度文件的大小:

spring.servlet.multipart.max-file-size=10 #单个文件最大的大小#多文件上传时,总的一次提交的最大大小,默认是10MB,改成100spring.servlet.multipart.max-request-size=100

二、原理

文件上传主动配置类是MultipartAutoConfigurationMultipartProperties所有无关文件上传的配置都封装在MultipartProperties里

  • springboot曾经主动配置好了StandardServletMultipartResolver【文件上传解析器】它只能解析规范的以servlet的形式,相当于以servlet协定上传过去的文件。如果是自定义形式,间接往上传流的形式应该写自定义的文件上传解析器

    @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;  }}

原理步骤
一、申请进来应用文件上传解析器判断(用isMultipart办法判断)并封装(用resolveMultipart办法封装,返回MultipartHttpServletRequest类型)文件上传申请

  • 1、申请被DispatcherServlet的doDispatch()拦挡
  • 在抉择应用哪个解析器去解决申请之前,会先调用checkMultipart()查看以后的申请是否是一个文件上传的申请

  • 2、checkMultipart()逻辑:
    判断multipartResolver文件上传解析器是否在容器中存在并且判断以后申请是否是文件上传申请

    文件上传解析器是否在容器中就看MultipartAutoConfiguration有没有给咱们把文件上传解析器放到容器中
  • 能够看到MultipartAutoConfiguration类里的multipartResolver()办法用了@ConditionalOnMissingBean注解,所以如果咱们没有写自定义的文件上传解析器的话,SpringBoot会主动往容器中注入StandardServletMultipartResolver文件上传解析器。
  • 判断以后申请是否是文件上传申请:

    public boolean isMultipart(HttpServletRequest request) {//判断申请的ContentType是否是multipart/结尾,因为咱们的表单设置enctype="multipart/form-data",所以这是一个文件上传的申请      return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");  }

  • 3、是一个文件上传申请的话,就用容器中的multipartResolver文件上传解析器解析申请

    return this.multipartResolver.resolveMultipart(request);

    并把原生的request封装成StandardMultipartHttpServletRequest类,而后所有的文件上传申请最终会被返回一个叫MultipartHttpServletRequest对象

    public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {      return new StandardMultipartHttpServletRequest(request, this.resolveLazily);  }

    因为后面的checkMultipart()会返回一个包装后的request申请,所以包装后的request申请和原生的request不相等,那么它就是一个文件上传申请,那么multipartRequestParsed的属性值也就变成了true


二、判断文件上传申请是用哪个参数解析器最终把参数值确定的

  • 1、找到这个参数解析器来解析申请中的文件内容并封装成MultipartFile
  • 2、找到该解析器的执行控制器办法

    /**   * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}   * if view resolution is required.   * @since 4.2   * @see #createInvocableHandlerMethod(HandlerMethod)   */@Nullable  protected ModelAndView invokeHandlerMethod(HttpServletRequest request,          HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {      invocableMethod.invokeAndHandle(webRequest, mavContainer);}

    一路step into后,能够看到一个这样的办法,这个办法上面有一个遍历循环的操作

    进入resolveArgument办法,看this.getArgumentResolver(parameter)如何获取参数解析器的


    拿到参数解析器后,就看他如何解析(RequestPartMethodArgumentResolver)



    进入getFiles办法

    再进入getMultipartFiles办法
    将request中的文件信息封装为一个Map并返回;MultiValueMap<String, MultipartFile>

因为参数的地位是这么写的,所以相当于将headerImg,photos全副封装到map中,想要获取headerImg里的数据能够从map里拿进去,photo也是。@PostMapping("/upload")public String upload(@RequestParam("email") String email,                     @RequestParam("username") String username,                     @RequestPart("headerImg") MultipartFile headerImg,                     @RequestPart("photos") MultipartFile[] photos)

最终是通过文件上传解析器把所有的文件信息获取到。


MultipartFile接口中还有另外十分弱小的办法

public interface MultipartFile extends InputStreamSource {//获取表单中的属性名    String getName();//获取原始的文件名    @Nullable    String getOriginalFilename();//获取内容的类型(multipart/form-data)    @Nullable    String getContentType();//判断以后文件是否为空    boolean isEmpty();//以后大小    long getSize();//获取字节流    byte[] getBytes() throws IOException;//获取输出流    InputStream getInputStream() throws IOException;//获取文件资源的门路信息    default Resource getResource() {        return new MultipartFileResource(this);    }//把文件保留到哪里    void transferTo(File var1) throws IOException, IllegalStateException;    default void transferTo(Path dest) throws IOException, IllegalStateException {//FileCopyUtils文件复制工具类        FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));    }}----------------FileCopyUtils------------------------------//把文件复制到哪里:实现文件流的拷贝public static int copy(File in, File out) throws IOException {        Assert.notNull(in, "No input File specified");        Assert.notNull(out, "No output File specified");        return copy(Files.newInputStream(in.toPath()), Files.newOutputStream(out.toPath()));    }

剖析原理从两处着手,一是这个性能springboot有没有为他做主动配置、主动配置了哪些,二是调试源码看这个性能是怎么实现的。