背景
需求
解決方案
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就是很好的一個案例說明,既不影響原有的功能,又實現了自己的需求。