文件上传接口接收不到文件入参
问题描述及现象
POST请求,表单形式传参,Controller层用@RequestParam注解接收;
但是发现接收不到入参;
排查思路
初步排查
1、参数名称是否一致
2、请求的Content-Type是否是 multipart/form-data
3、是否启用了multipart配置,spring.servlet.multipart.enabled=true
4、文件大小是否超过限制
初步排查后,以上四处均没问题;
进一步排查
问了下deepseek,ds告诉我可能是请求体被项目中的过滤器或拦截器提前消费了,所以传到controller层的时候已经没值了。
然后我检查了一遍项目中的过滤器、拦截器,感觉没有地方会提前消费请求体;并且梳理下了请求进来的顺序; 过滤器–>拦截器 prehandler() -->AOP前置通知–>Controller ;
然后我在过滤器这里打断点,检查参数是否获取到,发现也没有获取到,也就是说,请求一进来的时候,参数就没获取到;
深入排查
由于文件传输依赖的是MultipartResolver ,即Multipart解析器,所以我加了如下代码段来验证项目启动时是否正常装配了该解析器
@Autowired(required = false)
private MultipartResolver multipartResolver;@PostConstruct
public void checkResolver() {log.info("MultipartResolver: {}", multipartResolver); // 应输出 StandardServletMultipartResolver
}
经验证,项目启动时,MultipartResolver为null,没有正常装配。
既然没有正常装配,那我手动装配它。于是我加了以下代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;@Configuration
public class MultipartConfig {@Beanpublic MultipartResolver multipartResolver() {return new StandardServletMultipartResolver();}
}
ok,现在项目重新启动后,在上面的checkResolver()方法里打断点,这时发现multipartResolver有值了,说明装配成功了。但是令人意外的是,断点执行到Controller层的uploadFile接口,发现入参file还是null。
WHAT THE FUCK!
终极排查——源码分析
既然我手动装配这个解析器没用,那说明Spring不认我手动装配的这个解析器。
不过转过头来一想,讲道理MultipartResolver解析器是不需要手动装配的,只要配置文件里配了spring.servlet.multipart.enabled=true
,这个解析器就能在项目启动的时候被装配才对。
所以,这里我想的是,还是要解决MultipartResolver没有自动装配的问题。
事已至此,只能去看MultipartAutoConfiguration
的源码了。
从源码可以看到,
MultipartAutoConfiguration
装配的三个条件:
1.容器中有Servlet、StandardServletMultipartResolver、MultipartConfigElement
2.配置文件spring.servlet.multipart.enabled: true
3.应用启动的类型是SERVLET类型
MultipartConfigElement
装配的条件是只要Spring容器内不存在MultipartConfigElement的Bean
StandardServletMultipartResolver
装配的条件是只要Spring容器内不存在MultipartResolver的Bean
而Servlet
肯定也是有的,不然容器都不可能起来;
因此条件1是满足的; 而条件2和条件3显然也是满足的。
奇怪了。
既然自动装配的条件都满足,为什么自动装配没装成功?
这个时候我发现:项目里存在多个版本的Spring-boot-autoconfigure依赖。
明白了,十有八九是不同版本的Spring-boot-autoconfigure冲突导致的。
还有高手?
兴致冲冲地把项目里很多依赖的版本都统一了一下,确保子模块都是用的父pom里声明的版本号,并且maven也reload了好几遍,结果:他妈的Controller的file参数还是null。
实在无语,再次回到MultipartAutoConfiguration
的源码。
ctrl+鼠标左键,看看这个MultipartAutoConfiguration
在哪些地方被用到了。
在调用链中看到了令人气愤的一幕:原来MultipartAutoConfiguration
在另一个Controller中被排掉了。
而在此之前,我老早已经检查过模块的启动类中有没有排掉MultipartAutoConfiguration
,确认没有被排掉。不过怎么也没有想到有人竟会把排除自动装配类放在Controller层里,而不放在启动类里。简直了。
解决
把上图的exclude=MultipartAutoConfiguration.class 去掉,uploadFile接口就能正常接收file入参了。
归根结底还是历史遗留问题。