Spring Boot中MultipartFile实现原理机制详解

后端 潘老师 5天前 12 ℃ (0) 扫码查看

Spring Boot文件上传会使用到MultipartFile,下面深入了解一下它的运行机制、配置要点以及实际应用中的问题与解决方案。

一、MultipartFile解析流程

当客户端发起文件上传请求,也就是发送multipart/form-data请求时,整个处理流程如下:

  1. Servlet容器处理:Servlet容器接收到请求后,会启动HTTP协议解析工作。它根据请求头中的Content-Type来识别这是一个multipart类型的请求,然后创建临时存储结构。这个临时存储结构有可能在内存中,也可能在磁盘上,具体取决于后续配置。处理完这些后,Servlet容器会将解析后的数据封装成Request对象。
  2. Spring MVC解析:封装好的Request对象进入Spring MVC框架后,会触发MultipartResolver进行解析。这里有个关键的点要注意,只有在调用MultipartFile.getBytes()方法时,文件数据才会真正被加载到内存中。

用序列图表示的话,就是:

二、上传配置详解

在Spring Boot项目中,可以通过配置文件对文件上传进行设置。以YAML配置文件为例:

spring:  
  servlet:  
    multipart:  
      enabled: true  
      file-size-threshold: 1MB # 1MB 以下用内存,以上用磁盘  
      max-file-size: 5GB # 单个文件最大  
      max-request-size: 10GB # 整个请求最大  
      location: /data/tmp # 指定专用临时目录  
  • enabled:开启文件上传功能。
  • file-size-threshold:用于设定文件存储策略的阈值。小于这个阈值(1MB)的文件,会优先存储在内存中;大于这个阈值的文件,则会存储到磁盘上。
  • max-file-size:限制单个文件的最大上传大小。
  • max-request-size:规定整个上传请求的最大大小。
  • location:指定文件上传过程中临时存储的目录。

三、Feign上传文件的问题及解决方案

在实际开发中,使用Feign上传大文件时可能会遇到问题。比如,会出现“Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Required array length 2147483639 + 9 is too large”这样的报错信息。这是因为Feign在处理大文件上传时存在一些局限性,此时可以考虑使用RestTemplate来实现文件上传。

(一)调用方实现

/**  
 * 使用@LoadBalanced注解不能进行流式上传  
 * @return  
 */  
public RestTemplate getUploadRestTemplate() {  
    // 使用 Apache HttpClient 作为底层实现,使用 流式上传  
    CloseableHttpClient httpClient = HttpClients.custom()  
      .setMaxConnTotal(100) // 最大连接数  
      .setMaxConnPerRoute(20) // 每个路由的最大连接数  
      .build();  

    HttpComponentsClientHttpRequestFactory factory =  
        new HttpComponentsClientHttpRequestFactory(httpClient);  
    factory.setBufferRequestBody(false);  
    factory.setConnectTimeout(30_000); // 连接超时 30s  
    factory.setReadTimeout(300_000); // 读取超时 5min(大文件需延长)  
    return new RestTemplate(factory);  
}  
  
@PostMapping(value = "/file/upload")  
public String uploadStream(@RequestParam("file") MultipartFile file) throws IOException {  
    // 包装文件流为Resource(关键:实现contentLength()返回 -1)  
    int bufSize = 64*1024; //默认8kb  
    Resource resource = new InputStreamResource(new BufferedInputStream(file.getInputStream(),bufSize)) {  
        @Override  
        public long contentLength() {  
            return -1; // 触发分块传输  
        }  

        @Override  
        public String getFilename() {  
            return file.getName(); // 确保服务器能获取文件名  
        }  
    };  

    // 构建Multipart请求体  
    // 这里没有调用file.transferTo(),可能是原代码有误,正常上传文件需要处理文件存储逻辑

    // 设置请求头  
    HttpHeaders headers = new HttpHeaders();  
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);  
    ResponseEntity<String> response =  
        //手动进行负载  
        getUploadRestTemplate().exchange("http://localhost:8081/upload-file4?name="+file.getOriginalFilename(),  
        HttpMethod.POST,  
        new HttpEntity<>(resource,headers), String.class);  

    System.out.println(JSON.toJSONString(response));  

    return "success";  
}  

这里需要注意,添加了@LoadBalanced注解的RestTemplate会缓存请求数据,在处理大文件上传时容易导致内存溢出,所以在处理大文件上传场景时不能使用该注解。如果是在微服务系统中使用,就需要自行实现负载均衡逻辑。

(二)服务器方实现

//这样直接从request获取文件流,就不解析MultipartFile,效率也要好点  
@PostMapping(value = "/upload-file4", consumes = "application/octet-stream")  
public String uploadStream3(HttpServletRequest request,@RequestParam("name") String name) throws Exception {  
    try (InputStream inputStream = request.getInputStream()) {  
        Files.copy(inputStream, Paths.get("d:/" + System.currentTimeMillis() + "_" + name));  
        return "Upload success!";  
    }  
}  

这种方式直接从HttpServletRequest中获取文件流,绕过了MultipartFile的解析过程,在一定程度上提高了效率。

四、Spring对application/octet-stream的处理

Spring Boot在处理application/octet-stream请求类型时,主要使用ResourceHttpMessageConverter类。

  1. 读过程:它既支持流式读取数据,也支持将数据全部加载到字节数组中。不过,将数据全部加载到字节数组这种方式,在处理大文件时可能会导致内存溢出问题。
  2. 写过程:会先将数据写入一个字节数组中,同样存在内存溢出的风险。

在实际开发中,需要根据具体的业务场景和文件大小,合理选择处理方式,避免出现内存溢出等问题,确保系统的稳定运行。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/17600.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】