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

news/2024/5/20 0:13:24/文章来源:https://blog.csdn.net/htjl575896870/article/details/137078318

目录

前言

一. 自定义序列化器失效

1.1 @EnableWebMvc 的作用

1.2 @EnableWebMvc 带来了什么后果

1.3 原理分析

1.4 问题解决

二. 总结


前言

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

@Configuration
@EnableOpenApi
@EnableWebMvc
public class SwaggerConfig {@Beanpublic 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 {// 目标方法运行之前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle: " + request.getRequestURI());return true;}}

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

@EnableWebMvc@Configurationpublic class MyWebMvcConfig implements WebMvcConfigurer {public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyInterceptor());}}

3.定义Controller: 

@RestControllerpublic class MyController {@PostMapping("/hello")public User hello(@RequestBody User user){return user;}}

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

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

1.2 @EnableWebMvc 带来了什么后果

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

@Beanpublic 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@Overridepublic 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 {@Beanpublic RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();// 设置HttpMessageConverteradapter.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即可。

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

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

相关文章

基于javaweb宠物领养平台管理系统设计和实现

基于javaweb宠物领养平台管理系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源码联…

Android ddms在macOS上面卡死和Java版本异常无法关闭弹窗处理

背景 在macOS上面打开ddms工具遇到错误。产留的uix文件无法打开,弹出无法关闭和进入ddms无任何响应。 问题-无法关闭的弹窗 首先ddms在Android SDK中位置/sdk/tools/monitor这个二进制文件就是ddms程序了。 终端执行这个程序即可。第一个遇到的问题,打开ddms之后,弹出一个…

MySQL 高级语句(二)

一、子查询 1.1 相同表子查询 1.2 不同表/多表子查询 1.3 子查询的应用 1.3.1 语法 1.3.2 insert 子查询 1.3.3 update 子查询 1.3.4 delete 子查询 1.4 exists 关键字 1.4.1 true 1.4.2 false 1.5 as别名 二、视图 2.1 视图和表的区别和联系 2.1.1 区别 2.1.2 …

阿里云云服务器资源规格推荐指南

资源规格推荐可以根据您的特定业务场景&#xff0c;为您推荐最合适的计算资源规格以及满足您算力需求的资源规模。本文介绍如何根据物理机规格推荐ECS资源和根据总算力推荐ECS资源。 根据物理机规格推荐ECS资源 IDC上云可以帮助您在将线下IDC服务器搬迁上云前&#xff0c;根据…

单臂路由和三层交换机

目录 一.单臂路由 1.单臂路由的工作原理 2.单臂路由的配置 2.1画出拓扑图 2.2配置PC 2.3配置交换机 2.4配置路由器 2.5测试 二.三层交换机 1.三层交换机的概述 2.三层交换机的配置 2.1画出拓扑图 2.2配置PC 2.3配置二层交换机 2.4配置三层交换机 2.5测试 3.拓展 三.总结 一.…

iOS_convert point or rect 坐标和布局转换+判断

文章目录 1. 坐标转换2. 布局转换3. 包含、相交 如&#xff1a;有3个色块 let view1 UIView(frame: CGRect(x: 100.0, y: 100.0, width: 300.0, height: 300.0)) view1.backgroundColor UIColor.cyan self.view.addSubview(view1)let view2 UIView(frame: CGRect(x: 50.0, …

AI视频渲染原理是什么?

一、AI渲染原理 AI视频渲染是一种结合了人工智能技术的新型渲染方式&#xff0c;它主要通过深度学习和其他机器学习方法来优化传统渲染流程&#xff0c;以提高效率和质量。以下是AI视频渲染可能涉及的一些基本原理&#xff1a; 1. **智能采样**&#xff1a; - AI可以帮助决定在…

Git使用:实现文件在不同设备之间进行同步

一、注册Gitee&#xff0c;创建远程仓库 注册网址&#xff1a;登录 - Gitee.com 打开Gitee&#xff0c;注册完进行登录&#xff0c;点击右上角【】创建一个仓库 新建仓库&#xff1a; 点击创建&#xff0c;仓库创建完毕。 二、下载Git安装包&#xff0c;并创建本地仓库 下载网…

STM32串口收发单字节数据原理及程序实现

线路连接&#xff1a; 显示屏的SCA接在B11&#xff0c;SCL接在B10&#xff0c;串口的RX连接A9&#xff0c;TX连接A10。 程序编写&#xff1a; 在上一个博客中实现了串口的发送代码&#xff0c;这里实现串口的接收代码&#xff0c;在上一个代码的基础上增加程序功能。 Seiral.…

【数字图像处理matlab系列】数组索引

【数字图像处理matlab系列】数组索引 【先赞后看养成习惯】【求点赞+关注+收藏】 MATLAB 支持大量功能强大的索引方案,这些索引方案不仅简化了数组操作,而且提高了程序的运行效率。 1. 向量索引 维数为1xN的数组称为行向量。行向量中元素的存取是使用一维索引进行的。因此…

C语言数据结构基础——二叉树学习笔记(三)链式二叉树以及初步认识递归思想

1.链式二叉树概念及其逻辑 每个树都要看成&#xff1a;根&#xff0c;左子树&#xff0c;右子树 链表、顺序表中的遍历方式有正序遍历和逆序遍历&#xff0c;而我们在二叉树中&#xff0c;有前序遍历、中序遍历、后序遍历、层序等多种遍历方法。 所谓 二叉树遍历 (Traversal) …

[Java基础揉碎]单例模式

目录 什么是设计模式 什么是单例模式 饿汉式与懒汉式 饿汉式vs懒汉式 懒汉式存在线程安全问题 什么是设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模式就像是经典的棋谱&am…

(三)Ribbon负载均衡

1.1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的组件&#xff0c;来实现负载均衡功能的。 1.2.源码跟踪 为什么我们只输入了service名称就可以访问了呢&#xff1f;之前还要获取ip和端口。 显然有人帮我们根据service名称&#xff0c;获取到了服务实例的ip和…

C++ vector容器类型

vector类为内置数组提供了一种替代表示&#xff0c;与string类一样 vector 类是随标准 C引入的标准库的一部分 &#xff0c;为了使用vector 我们必须包含相关的头文件 &#xff1a; #include <vector> 使用vector有两种不同的形式&#xff0c;即所谓的数组习惯和 STL习…

MySQL - 高阶语句(二)

目录 6. 子查询 操作&#xff1a; EXISTS 关键字 别名 as 7. 视图 理论&#xff1a; 操作&#xff1a; 视图的优缺点 8. NULL 值 9. union 联级 9.1 union &#xff08;合并后去重&#xff09; 9.2 union all (合并后不去重) 9.3 取非交集值 10. case 条件选择查…

React中 类组件 与 函数组件 的区别

类组件 与 函数组件 的区别 1. 类组件2. 函数组件HookuseStateuseEffectuseCallbackuseMemouseContextuseRef 3. 函数组件与类组件的区别3.1 表面差异3.2 最大不同原因 1. 类组件 在React中&#xff0c;类组件就是基于ES6语法&#xff0c;通过继承 React.component 得到的组件…

centos7.9安装mysql

1. 概述 官网&#xff1a;https://www.mysql.com/ MySQL是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;MySQL是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的RDBMS (Relational Database Management S…

中国土壤厚度空间分布数据

土壤层次分为覆盖层 林溶层 淀积层 母质层&#xff0c;其中在林溶层中的最上面那层就是我们通常说的土壤厚度在这一层中&#xff0c;这一层也被称为腐殖层&#xff0c;是肥力性质最好的一层&#xff0c;植物根系和微生物也集中在这一层。至于覆盖层在森林土壤中比较常见&#x…

c语言应该怎么系统的学习?

c语言应该怎么系统的学习? 系统性学习C语言&#xff0c;这个描述首先是给人目标不明确&#xff0c;概念不清晰的映像。在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后…

Anaconda的GEE环境中安装torch库

打开Anaconda&#xff0c;点击运行&#xff0c;打开terminal 输入pip install torch 而且由于anaconda中自己配置好了镜像源&#xff0c;在pip时自动使用清华镜像源