当前位置:   article > 正文

@EnableWebMvc 导致自定义序列化器失效

@EnableWebMvc 导致自定义序列化器失效

目录

前言

一. 自定义序列化器失效

1.1 @EnableWebMvc 的作用

1.2 @EnableWebMvc 带来了什么后果

1.3 原理分析

1.4 问题解决

二. 总结


前言

在使用Swagger的时候用 到了@EnableWebMvc,发现之前为了解决Long类型、日期类型等自定义序列化器失效了

  1. @Configuration
  2. @EnableOpenApi
  3. @EnableWebMvc
  4. public class SwaggerConfig {
  5. @Bean
  6. public Docket api() {
  7. return new Docket(DocumentationType.OAS_30)
  8. .select()
  9. .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
  10. .paths(PathSelectors.any())
  11. .build();
  12. }
  13. }

Swagger3/2+Spring boot 使用小结_spring boot3 + swagger3-CSDN博客

我们有时候,可能需要自定义一个序列化器来满足自己的需要,但是如果项目中不正确使用了@EnableWebMvc注解,可能会导致这个自定义的序列化器失效。

一. 自定义序列化器失效

首先我们应该看下@EnableWebMvc这个注解是拿来干啥的吧。

1.1 @EnableWebMvc 的作用

@EnableWebMvc用于快捷配置SpringWebMVC。用于自定义MVC的相关配置用的。相当于xml配置:

<mvc:annotation-driven/>

当我们需要自定义实现MVC的时候,有三种选择:

  • 实现WebMvcConfigurer接口
  • 继承WebMvcConfigurerAdapter
  • 继承WebMvcConfigurationSupport

我们这里通过一个案例来更直观的看这个注解。本文通过第一种方式来实现。

1.我们自定义一个拦截器MyInterceptor

  1. public class MyInterceptor implements HandlerInterceptor {
  2. // 目标方法运行之前执行
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. System.out.println("preHandle: " + request.getRequestURI());
  6. return true;
  7. }
  8. }

 2.自定义MVC配置:添加我们刚刚定义好的拦截器。

  1. @EnableWebMvc
  2. @Configuration
  3. public class MyWebMvcConfig implements WebMvcConfigurer {
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. registry.addInterceptor(new MyInterceptor());
  6. }
  7. }

3.定义Controller: 

  1. @RestController
  2. public class MyController {
  3. @PostMapping("/hello")
  4. public User hello(@RequestBody User user){
  5. return user;
  6. }
  7. }

4.访问对应的路径,就能在控制台上看到信息: 

 还可以配置其他的一些功能,例如:视图解析器、静态资源映射等等。 

1.2 @EnableWebMvc 带来了什么后果

假设我们这个项目使用了fastjson来作为默认的转换器,

  1. @Bean
  2. public HttpMessageConverters fastJsonHttpMessageConverters() {
  3. FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
  4. FastJsonConfig fastJsonConfig = new FastJsonConfig();
  5. fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
  6. fastConverter.setFastJsonConfig(fastJsonConfig);
  7. HttpMessageConverter<?> converter = fastConverter;
  8. return new HttpMessageConverters(converter);
  9. }

 然后我们访问下案例的接口,结果:

我们知道,fastjson默认情况下是不会输出null这个结果的,会被过滤掉,并且我们自定义序列化器的时候也没有去指定SerializerFeature.WriteMapNullValue属性。那么问题来了,底层进行解析的时候,到底用的是什么转换器?难道不是我们自定义的fastjson吗?


Spring常见问题解决 - Body返回体中对应值为null的不输出?这篇文章的基础上,我们直接定位到转换器的代码部分,看下返回结果最终用的是什么序列化器:

总结下就是:自定义序列化器失效了。 当然,咱们这里为止,我是基于我知道底层原理的情况下,指明了这个问题是由于@EnableWebMvc的使用引起的。那么接下来就开始分析。

1.3 原理分析

首先说下本质原因:@EnableWebMvc 导致SpringBoot中 WebMvc的自动配置失效。

再从代码角度来看,我们先看下@EnableWebMvc 注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(DelegatingWebMvcConfiguration.class)
  5. public @interface EnableWebMvc {
  6. }

 这里引入了DelegatingWebMvcConfiguration类。而他属于WebMvcConfigurationSupport的子类:

 
  1. @Configuration(proxyBeanMethods = false)

  2. public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

另一方面,SpringBoot实际上是整合了MVC的功能的,主要通过自动装配机制来完成功能的加载,入口在于:WebMvcAutoConfiguration类中。

 
  1. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

  2. public class WebMvcAutoConfiguration {}

可以发现,自动装配里面,引入了WebMvcConfigurationSupport这个类。只不过是通过@ConditionalOnMissingBean注解来注入的。注意了,这个注解的用处在于:

  • 当这个类型的Bean被注册之后,就不会再注册。它会保证你的Bean只有一个。
  • 也就是说WebMvcConfigurationSupport类型的(包括它的子类)Bean只能有一个。
  • 即如果我们使用了@EnableWebMvc 注解,就会和SpringBoot对于MVC的自动装配产生冲突,因为其注入了DelegatingWebMvcConfiguration类,属于WebMvcConfigurationSupport类的子类。
  • 如果存在@EnableWebMvc 注解,优先以我们自定义的MVC配置为主。

那么问题来了,我们从上一篇文章Spring常见问题解决 - Body返回体中对应值为null的不输出?中得到一个点就是:Spring是通过ObjectMapper对象进行请求和返回体的转换的。

那么@EnableWebMvc和他有啥子关系呢?我们再回到@EnableWebMvc本身。我们根据上文得知,它会引入一个WebMvcConfigurationSupport的子类。我们看下这个父类中的代码:

  1. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
  2. public class WebMvcAutoConfiguration {
  3. @Configuration(proxyBeanMethods = false)
  4. @Import(EnableWebMvcConfiguration.class)
  5. @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
  6. @Order(0)
  7. public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
  8. }
  9. }

可以发现有个静态内部类WebMvcAutoConfigurationAdapter。它通过@Import注解引入了EnableWebMvcConfiguration

  1. @Configuration(proxyBeanMethods = false)
  2. public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
  3. @Bean
  4. @Override
  5. public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
  6. @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  7. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  8. @Qualifier("mvcValidator") Validator validator) {
  9. RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
  10. conversionService, validator);
  11. adapter.setIgnoreDefaultModelOnRedirect(
  12. this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
  13. return adapter;
  14. }
  15. }

这个又引入了RequestMappingHandlerAdapter类:我们关注requestMappingHandlerAdapter()函数:

  1. RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
  2. conversionService, validator);
  3. public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  4. @Bean
  5. public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
  6. @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  7. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  8. @Qualifier("mvcValidator") Validator validator) {
  9. RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
  10. // 设置HttpMessageConverter
  11. adapter.setMessageConverters(getMessageConverters());
  12. // ..
  13. return adapter;
  14. }
  15. ↓↓↓
  16. protected final List<HttpMessageConverter<?>> getMessageConverters() {
  17. if (this.messageConverters == null) {
  18. // ...
  19. addDefaultHttpMessageConverters(this.messageConverters);
  20. }
  21. return this.messageConverters;
  22. }
  23. ↓↓↓
  24. // 添加默认的消息转换器
  25. protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
  26. messageConverters.add(new ByteArrayHttpMessageConverter());
  27. messageConverters.add(new StringHttpMessageConverter());
  28. messageConverters.add(new ResourceHttpMessageConverter());
  29. messageConverters.add(new ResourceRegionHttpMessageConverter());
  30. try {
  31. messageConverters.add(new SourceHttpMessageConverter<>());
  32. }
  33. catch (Throwable ex) {
  34. // Ignore when no TransformerFactory implementation is available...
  35. }
  36. messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  37. if (romePresent) {
  38. messageConverters.add(new AtomFeedHttpMessageConverter());
  39. messageConverters.add(new RssChannelHttpMessageConverter());
  40. }
  41. if (jackson2XmlPresent) {
  42. Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
  43. if (this.applicationContext != null) {
  44. builder.applicationContext(this.applicationContext);
  45. }
  46. messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
  47. }
  48. else if (jaxb2Present) {
  49. messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
  50. }
  51. // 这里还能发现,jackson优先级高于gson。
  52. if (jackson2Present) {
  53. Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
  54. if (this.applicationContext != null) {
  55. builder.applicationContext(this.applicationContext);
  56. }
  57. messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
  58. }
  59. else if (gsonPresent) {
  60. messageConverters.add(new GsonHttpMessageConverter());
  61. }
  62. else if (jsonbPresent) {
  63. messageConverters.add(new JsonbHttpMessageConverter());
  64. }
  65. if (jackson2SmilePresent) {
  66. Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
  67. if (this.applicationContext != null) {
  68. builder.applicationContext(this.applicationContext);
  69. }
  70. messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
  71. }
  72. if (jackson2CborPresent) {
  73. Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
  74. if (this.applicationContext != null) {
  75. builder.applicationContext(this.applicationContext);
  76. }
  77. messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
  78. }
  79. }
  80. }

总而言之就是,默认的解析器里面不包含我们自定义的fastjson。因此在进行HTTP请求的时候,对结果进行反序列化输出的时候,使用的序列化器是jackson

1.4 问题解决

解决方式很简单,我们只需要将@EnableWebMvc注解去掉即可。去掉后重启下项目,我们看下结果:

可以发现确实反序列化的时候使用的是fastjson而不是jackson了:

再看下我们自定义的拦截器是否生效了:

二. 总结

  • 项目中,如果我们希望自定义一些MVC的功能,我们只需要实现WebMvcConfigurer接口即可。无需添加@EnableWebMvc注解。
  • 添加@EnableWebMvc注解,会导致SpringBootMVC的自动装配失效。因为Spring对于WebMvcConfigurationSupport类型的Bean只允许存在一个(包括其子类)。
  • 此时以序列化器为例,使用@EnableWebMvc注解会导致自定义的序列化器失效。例如本文案例的fastjson。而Spring源码中对于默认注入的序列化器类型中并不包含fastjson
  • Spring官网就已经说了,针对于SpringBoot而言,项目已经对MVC进行自动装配了,因此在自定义MVC功能的时候,不要使用@EnableWebMvc注解。加一个@Configuration即可。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/328675
推荐阅读
相关标签
  

闽ICP备14008679号