背景
需求
解决方案
public ReturnJson saveImg(@RequestParam String advertisement , @RequestParam MultipartFile file) {
public void createAdvertisement(@RequestPart @Validated Advertisement advertisement, @RequestPart MultipartFile file) { System.out.println("上传成功"); }
问题发现
{ "timestamp": 1637578285092, "status": 415, "error": "Unsupported Media Type", "message": "", "path": "xxxx" }
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver
解决过程
根据异常抛出的地方,定位到 AbstractMessageConverterMethodArgumentResolver . readWithMessageConverters方法。
HttpMessageConverters 默认有10 个消息转换器实现类,能够处理不同类型的消息。依次调用这些convert,能够处理就处理,最后没有一个convert 处理就会抛出异常。
也就是说已有的 HttpMessageConverters 解析不了octet-stream,最后就抛出了异常:
源码上有个特别的点,如果获取不到 contentType,那就默认设置为 MediaType.APPLICATION_OCTET_STREAM;
try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; }
根本原因就是没有一个HttpMessageConverter 能够处理 APPLICATION_OCTET_STREAM 的格式,所以根本解决答案就是手动添加一个HttpMessageConverter就可以了。可以添加一个FastJsonConverterConfig,实现方式有两种:
方法一:配置方式
@Bean public JavaSerializationConverter javaSerializationConverter() { return new JavaSerializationConverter(); }
springboot会把我们自定义的converter放在顺序上的最高优先级,优先使用我们这个。
方式二:实现WebMvcConfigurer,覆写相关方法:
@Configuration public class InterceptorAdapterConfig implements WebMvcConfigurer { void configureMessageConverters(List<HttpMessageConverter<?>> converters) { } void extendMessageConverters(List<HttpMessageConverter<?>> converters) { }
我这里就采用第一种方式,代码如下
@Configuration public class FastJsonConverterConfig { @Bean public HttpMessageConverters fastJsonHttpMessageConverters() { FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures( SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullBooleanAsFalse ); fastConverter.setFastJsonConfig(fastJsonConfig); fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); fastConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_OCTET_STREAM)); HttpMessageConverter<?> converter = fastConverter; return new HttpMessageConverters(converter); } }
fastConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM));
表示支持 MediaType.APPLICATION_OCTET_STREAM 类型的解析。重新启动服务,发现自定义的序列化类果然优先级最高。
最后采用formdata 格式请求可以成功接收到并且解析数据:
问题复现
@Configuration @EnableWebMvc @Slf4j public class InterceptorAdapterConfig implements WebMvcConfigurer {
@EnableWebMvc 是罪魁祸首
我们去看下EnableWebMvc 的原理:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
他导入DelegatingWebMvcConfiguration 这个配置类,继续跟下去
@Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }
setConfigurers 方法表明了会将所有WebMvcConfigurer 的实现类注入进来。DelegatingWebMvcConfiguration是WebMvcConfigurationSupport的一种实现,其主要目的是提供 MVC 配置。通过前面的解释,其实就是通过将 @EnableWebMvc 添加到应用程序 @Configuration 类来导入。 看到这里似乎并没有发现有什么冲突。
既然这个配置类没有什么特殊的,我们换个思路,从spring mvc 的自动配置入手,看看有没有什么问题,也就是WebMvcAutoConfiguration 配置类
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration {
哦豁,还真的有发现,我们看到有个条件配置类
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
没有WebMvcConfigurationSupport 这个配置类时,WebMvcAutoConfiguration才会生效。恍然大悟了,@EnableWebMvc 确实会使得spring mvc的自动配置失效,EnableWebMvc 更多适用于想要定制化处理,但是我们既然使用了springmvc 框架,又不利用其封装好的特性,反而自己去写实现,这不是多此一举吗。更多的是,我们基于其特性,做一些必要的兼容,修改。而对修改开放这正是spring 的强大之处,比如前面的加入自定义的FastJsonHttpMessageConverter就是很好的一个案例说明,既不影响原有的功能,又实现了自己的需求。