Spring 启动 2.5.x:所需的请求部分 'file' 不存在

Spring Boot 2.5.x: Required request part 'file' is not present

我有一个文件正在上传 api,它在 spring boot 版本 2.1.13 下工作得很好。升级版本到2.5.2后,开始报异常。查看更改日志,我看不到任何与 Multipart 处理相关的重大更改。我能在这里错过什么?下面是我的示例代码。

异常

org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present
    at org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.resolveArgument(RequestPartMethodArgumentResolver.java:161) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) ~[spring-web-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.3.8.jar:5.3.8]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.3.8.jar:5.3.8]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) [tomcat-embed-core-9.0.48.jar:4.0.FR]

application.properties

spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB

控制器端点

@PostMapping(
    value = "/upload", 
    consumes = MediaType.MULTIPART_FORM_DATA_VALUE, 
    produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<Object> uploadFile(@RequestPart("file") MultipartFile file) {
...
}

请求负载样本

POST http://localhost:8080/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGG9dgUb5THDV0eDB

------WebKitFormBoundaryGG9dgUb5THDV0eDB
Content-Disposition: form-data; name="file"; filename="Sample.pdf"
Content-Type: application/pdf

------WebKitFormBoundaryGG9dgUb5THDV0eDB--

注意: 我的配置中没有定义任何 MultipartResolver bean。我尝试按如下方式添加 MultipartResolver bean 定义(一次仅添加一个),但似乎没有解决问题。

@Bean
public CommonsMultipartResolver multipartResolver() { // didn't work
    return new CommonsMultipartResolver();
}

@Bean
public StandardServletMultipartResolver multipartResolver() { // didn't work
    return new StandardServletMultipartResolver();
}

原来这个问题是在 Spring Boot 2.2 之后受到影响的。从那个版本开始,过滤器 HttpHiddenMethodFilter 默认被禁用。在 application.properties.

中启用过滤器后问题得到解决
spring.mvc.hiddenmethod.filter.enabled=true

我的详细发现

上述过滤器的用途与我遇到的错误无关。但是作为执行过滤器的副作用,请求 parts 正在初始化。更具体地说,当过滤器尝试检索 _method 参数值 (e.g. request.getParameter("_method") 时,request 实例的 getParameter 方法在内部似乎解析参数,然后初始化请求 parts。所以当过滤器在spring-boot 2.2 版本中被禁用时,没有任何东西可以初始化请求parts

我觉得请求 parts 初始化应该在 Spring 框架本身内修复。但在那之前,要么我们可以启用 HttpHiddenMethodFilter 过滤器,要么我们可以定义一个自定义过滤器来负责初始化请求 parts,如下所示:

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestInitializerFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        request.getParameterNames(); // this takes care of initializing request `parts`

        filterChain.doFilter(request, response);
    }
}

@RequestPart 默认为 required=true(查看文档)。似乎 HttpHiddenMethodFilter 将值初始化为副作用。 spring 更新改变了这种行为。 要解决此问题,请设置 required=false 并在必要时初始化该值。