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

目录

前言

一. 自定义序列化器失效

1.1 @EnableWebMvc 的作用

1.2 @EnableWebMvc 带来了什么后果

1.3 原理分析

1.4 问题解决

二. 总结


前言

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

@Configuration
@EnableOpenApi
@EnableWebMvc
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
                .paths(PathSelectors.any())
                .build();
    }
}

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

public class MyInterceptor implements HandlerInterceptor {

// 目标方法运行之前执行

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

System.out.println("preHandle: " + request.getRequestURI());

return true;

}

}

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

@EnableWebMvc

@Configuration

public class MyWebMvcConfig implements WebMvcConfigurer {

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new MyInterceptor());

}

}

3.定义Controller: 

@RestController

public class MyController {

@PostMapping("/hello")

public User hello(@RequestBody User user){

return user;

}

}

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

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

1.2 @EnableWebMvc 带来了什么后果

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

@Bean

public HttpMessageConverters fastJsonHttpMessageConverters() {

FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

FastJsonConfig fastJsonConfig = new FastJsonConfig();

fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);

fastConverter.setFastJsonConfig(fastJsonConfig);

HttpMessageConverter<?> converter = fastConverter;

return new HttpMessageConverters(converter);

}

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

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


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

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

1.3 原理分析

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

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

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Import(DelegatingWebMvcConfiguration.class)

public @interface EnableWebMvc {

}

 这里引入了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的子类。我们看下这个父类中的代码:

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

public class WebMvcAutoConfiguration {

@Configuration(proxyBeanMethods = false)

@Import(EnableWebMvcConfiguration.class)

@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })

@Order(0)

public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

}

}

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

@Configuration(proxyBeanMethods = false)

public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

@Bean

@Override

public RequestMappingHandlerAdapter requestMappingHandlerAdapter(

@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,

@Qualifier("mvcConversionService") FormattingConversionService conversionService,

@Qualifier("mvcValidator") Validator validator) {

RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,

conversionService, validator);

adapter.setIgnoreDefaultModelOnRedirect(

this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());

return adapter;

}

}

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

RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,

conversionService, validator);


public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {

@Bean

public RequestMappingHandlerAdapter requestMappingHandlerAdapter(

@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,

@Qualifier("mvcConversionService") FormattingConversionService conversionService,

@Qualifier("mvcValidator") Validator validator) {


RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();

// 设置HttpMessageConverter

adapter.setMessageConverters(getMessageConverters());

// ..

return adapter;

}

↓↓↓

protected final List<HttpMessageConverter<?>> getMessageConverters() {

if (this.messageConverters == null) {

// ...

addDefaultHttpMessageConverters(this.messageConverters);

}

return this.messageConverters;

}

↓↓↓

// 添加默认的消息转换器

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {

messageConverters.add(new ByteArrayHttpMessageConverter());

messageConverters.add(new StringHttpMessageConverter());

messageConverters.add(new ResourceHttpMessageConverter());

messageConverters.add(new ResourceRegionHttpMessageConverter());

try {

messageConverters.add(new SourceHttpMessageConverter<>());

}

catch (Throwable ex) {

// Ignore when no TransformerFactory implementation is available...

}

messageConverters.add(new AllEncompassingFormHttpMessageConverter());


if (romePresent) {

messageConverters.add(new AtomFeedHttpMessageConverter());

messageConverters.add(new RssChannelHttpMessageConverter());

}


if (jackson2XmlPresent) {

Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();

if (this.applicationContext != null) {

builder.applicationContext(this.applicationContext);

}

messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));

}

else if (jaxb2Present) {

messageConverters.add(new Jaxb2RootElementHttpMessageConverter());

}

// 这里还能发现,jackson优先级高于gson。

if (jackson2Present) {

Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();

if (this.applicationContext != null) {

builder.applicationContext(this.applicationContext);

}

messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));

}

else if (gsonPresent) {

messageConverters.add(new GsonHttpMessageConverter());

}

else if (jsonbPresent) {

messageConverters.add(new JsonbHttpMessageConverter());

}


if (jackson2SmilePresent) {

Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();

if (this.applicationContext != null) {

builder.applicationContext(this.applicationContext);

}

messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));

}

if (jackson2CborPresent) {

Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();

if (this.applicationContext != null) {

builder.applicationContext(this.applicationContext);

}

messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));

}

}

}

总而言之就是,默认的解析器里面不包含我们自定义的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即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/493854.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript、ES6与微信小程序:工具箱、升级与新房子

JavaScript、ES6和微信小程序三者之间有什么联系&#xff1f;我想&#xff0c;作为初学者还是有点蒙。下面作一个简单的分析&#xff0c;供大家参考。 首先,我们可以把JavaScript想象成一个非常强大的工具箱,里面装满了各种各样的工具。这些工具可以帮助我们完成各种任务,比如…

vivado 使用远程主机和计算群集

使用远程主机和计算群集 概述 AMD Vivado™集成设计环境&#xff08;IDE&#xff09;支持同时并行合成和实现的执行在多个Linux主机上运行。你可以做到这一点通过配置单个主机或指定要在其上启动作业的命令手动执行现有的计算集群。目前&#xff0c;Linux是Vivado唯一支持远程主…

书生浦语大模型实战营第一课笔记

书生浦语大模型全链路开源体系 课程笔记大模型的发展趋势InternLM2的主要亮点模型到应用的典型流程全链路的开源工具 InternLM2技术报告笔记大型语言模型的发展InternEvoModel Structure训练数据 课程笔记 第一节课主要对大模型进行介绍&#xff0c;特别是书生浦语大模型的发展…

【读书微言】The first summary

系列文章目录 文章目录 系列文章目录前言一、读书微言总结 前言 一、读书微言 强大的内心催生信念我们的生活是否幸福并不是依靠外在的环境&#xff0c;而是依靠我们内在的信念。要想成为自己命运的主宰&#xff0c;我们就必须形成自己的信念。只要我们能坚定自己的内在信念&…

认识线程(Thread)

目录 一、概念 1、 线程是什么 2、为啥要有线程&#xff1f; 3、进程和线程的区别 4、Java 的线程 和 操作系统线程 的关系 二、第⼀个多线程程序 三、创建线程 ⽅法1&#xff1a;继承 Thread 类 ⽅法2&#xff1a;实现 Runnable 接⼝ 对⽐上⾯两种⽅法: 其他变形 四、…

鸿蒙应用开发学习:用Marquee组件做个跑马灯

一、前言 鸿蒙应用的学习持续进行中&#xff0c;这两天阅读官方的API参考文档&#xff0c;发现一个有趣的组件——Marquee&#xff0c;用它做了个跑马灯&#xff0c;做个学习记录。 二、参考资料 官网文档链接如下&#xff1a; https://developer.huawei.com/consumer/cn/d…

【JVM】Java八股文之JVM篇

目录 一、JVM类加载与垃圾回收加载过程加载机制优点图解加载机制 分代回收分代垃圾回收新生代垃圾回收老年代垃圾回收 回收算法 一、JVM类加载与垃圾回收 面试过程中最经典的一题&#xff1a; 请你讲讲在JVM中类的加载过程以及垃圾回收&#xff1f; 加载过程 当Java虚拟机&…

双纤SFP光模块和单纤SFP光模块之间的区别

双纤SFP光模块和单纤SFP光模块是两种不同的光模块类型。对于网络部署而言&#xff0c;了解它们之间的区别至关重要。本文将深入探讨这两种光模块之间的差异&#xff0c;并介绍其特性和适用场景。 双纤与单纤SFP光模块&#xff1a;它们是什么&#xff1f; 双纤SFP光模块是常用…

回文子串 每日温度 接雨水

647. 回文子串 力扣题目链接 如果s【i】和s【j】相同 dp【i1】【j-1】也是回文串的话 &#xff08;等于true&#xff09; 那么dp【i】【j】也是回文串 true 定义一个bool二维数组 遍历顺序是从下到上 从左到右 因为dp【i】【j】是通过dp【i1】【j-1】推出来的 i从最后一…

120.龙芯2k1000-qt(19)-做了一个qt测试界面

主要接口和性能测试&#xff0c;主要针对的是龙芯2k1000. 以下是windows下的截图&#xff0c;大概功能就是这样吧&#xff0c;能想到的都想了一遍。 cpu的温度和频率采集不到&#xff0c;就没有放了。

冒泡排序(六大排序)

冒泡排序 冒泡排序的特性总结&#xff1a; 1. 冒泡排序是一种非常容易理解的排序 2. 时间复杂度&#xff1a;O(N^2) 3. 空间复杂度&#xff1a;O(1) 4. 稳定性&#xff1a;稳定 动图分析&#xff1a; 代码实现&#xff1a; Swap(int*p1,int*p2) {int tmp *p1;*p1*p2…

程序员35岁的职业困惑及应对之道

35岁,对许多程序员来说,是一个职业生涯的重要分水岭。在这个年龄,一些人开始感到迷茫和焦虑,担心自己的技能已经落后,难以跟上日新月异的技术变革。而另一些人则充满信心,认为多年来积累的丰富经验和扎实的技术功底,将助力他们在未来的职业道路上取得新的飞跃。 无疑,在AI、自…

【Flutter 面试题】 Flutter中的路由(Route)是什么?如何在应用程序中实现路由导航?

【Flutter 面试题】 Flutter中的路由&#xff08;Route&#xff09;是什么&#xff1f;如何在应用程序中实现路由导航&#xff1f; 文章目录 写在前面口述回答补充说明 写在前面 &#x1f64b; 关于我 &#xff0c;小雨青年 &#x1f449; CSDN博客专家&#xff0c;GitChat专栏…

电商产品效果图渲染用什么工具更方便?

​在电子商务的快速发展中&#xff0c;产品的视觉呈现变得至关重要。对于电商行业的设计师而言&#xff0c;选择一款既便捷又高效的渲染工具&#xff0c;对于快速完成高质量的产品效果图至关重要。特别是对于初学者&#xff0c;工具的直观性和功能性是他们最为关注的焦点。 那…

在线接口文档预言方案

在线接口文档预言方案 要求&#xff1a; ​ 支持自动生成接口文档 ​ 能够支持在线测试(http&#xff0c;websocket) ​ 对代码没有侵入性 一、目前涉及的相关技术收集 sudo apt update #更新数据 sudo apt upgrade #更新软件 sudo apt install openssh-server #下载安装…

鸿蒙HarmonyOS应用开发之Node-API常见问题

ArkTS/JS侧import xxx from libxxx.so后&#xff0c;使用xxx报错显示undefined/not callable 排查.cpp文件在注册模块时的模块名称与so的名称匹配一致。 如模块名为entry&#xff0c;则so的名字为libentry.so&#xff0c;napi_module中nm_modname字段应为entry&#xff0c;大小…

844. 走迷宫 典bfs

AC代码&#xff1a; #include<algorithm> #include<iostream> #include<cstring> #include<queue> #include<algorithm> #include<cmath> using namespace std; const int N 110;int mp[N][N]; int sx,sy; bool vis[N][N]; struct node{i…

2024年热门游泳耳机推荐!公认最佳的4大游泳耳机分享,好用不贵

随着科技的发展&#xff0c;游泳运动已经不仅仅是一项健身活动&#xff0c;更是一种生活方式。在游泳过程中&#xff0c;音乐的陪伴能够让我们更好地享受这项运动&#xff0c;同时也能提高我们的游泳效果。因此&#xff0c;选择一款适合自己的游泳耳机显得尤为重要。 然而&…

嵌入式和 Java 走哪条路?

最近看到一个物联网大三学生的疑问&#xff0c;原话如下&#xff1a; 本人普通本科物联网工程专业&#xff0c;开学大三&#xff0c;现在就很迷茫&#xff0c;不打算考研了&#xff0c;准备直接就业&#xff0c;平时一直在实验室参加飞思卡尔智能车比赛&#xff0c;本来是想走嵌…