如何让SpringMVC框架使用我们封装的JsonUtils实现消息的序列化和反序列化
关注 “Java艺术” 我们一起成长!
你好!我是吴就业,一名普普通通的Java开发者。
这是我的第165
篇原创文章,欢迎阅读!
spring mvc
默认使用的json
序列化和反序列工具是jackson
,虽然我们项目中也是默认使用jackson
,但由于一些历史项目存在日期格式不统一问题,我们需要自定义针对日期类型字段自适应解析,支持将时间戳、各种格式日期字符串都能解析为Date
类型实例、LocalDate
、LocalDateTime
类型实例。
除此之外,笔者选择自己封装json
解析框架,项目中引入了哪个json
框架的依赖,JsonUtils
工具类就自动切换使用哪个框架,因此笔者约定禁用json
框架的注解,统一使用Java
的transient
关键字声明不需要序列化和反序列的字段,以适配各种框架,也适配Java
的序列化。
综上,我们不得不想办法让Spring MVC
框架使用我们封装的JsonUtils
实现交互消息的序列化和反序列化。
那么,如何让Spring MVC
框架使用我们封装的JsonUtils
实现交互消息的序列化和反序列化?
替换SpringMVC消息转换器
Spring MVC
在接收到请求时,先根据url
找到HandlerMethod
,在调用HandlerMethod
之前,会先根据请求头的Content-Type
将“请求body
”转换为Java
对象,例如json
反序列化。
在调用HandlerMethod
之后,根据响应头的Content-Type
将返回值转换为“响应body
”返回给客户端,例如完成json
序列化。这些操作称为消息转换,由http
消息转换器完成。
HttpMessageConverter
:处理http
请求的消息转换器,根据http
协议数据包的请求或响应头的Content-Type
将body
解析为Java
对象。
HandlerMethodArgumentResolver
:方法参数解析器,负责调用消息转换器将“请求body
”转换为Java
对象,每个解析器有每个解析器的职责,使用策略模式实现。
例如RequestResponseBodyMethodProcessor
负责解析被@RequestBody
注解注释的参数,调用消息转换器将“请求body
”转换为对应方法参数类型的对象;
HandlerMethodReturnValueHandler
:方法返回值处理器,负责调用消息转换器将方法返回值转换为“响应body
”,每个解析器有每个解析器的职责,使用策略模式实现。
例如,当方法或者类上存在@ResponseBody
注解时,由RequestResponseBodyMethodProcessor
调用消息转换器将返回值解析为“响应body
”返回给客户端。
我们看下消息转换器接口:HttpMessageConverter
。
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
-
getSupportedMediaTypes
:获取该消息转换器支持的Content-Type
(MediaType
); -
canRead
:根据请求的Content-Type
判断自己是否支持读取该类型的消息; -
canWrite
:根据响应的Content-Type
判断自己是否支持写该类型的消息; -
read
:读取消息,将请求body
转换为Java
对象; -
write
:写消息,将响应的Java
对象转换为“响应body
”;
其中getSupportedMediaTypes
、canRead
|canWrite
方法用于实现策略模式,HttpMessageConverter
可以有多个,而Spring MVC
每次处理消息的转换,只会使用第一个canRead
|canWrite
方法返回true
的消息转换器,在转换请求消息时使用canRead
,在转换响应消息时使用canWrite
。
Spring MVC
默认使用MappingJackson2HttpMessageConverter
完成Content-Type
为application/json
的消息转换,即完成序列化和反序列化。
要让Spring MVC
框架使用我们自己封装的JsonUtils
解析Content-Type
为application/json
的请求body
或响应body
,就要实现自定义HttpMessageConverter
,并替换MappingJackson2HttpMessageConverter
。
如何替换?
使用WebMvcConfigurationSupport
。
原理:当WebMvcConfigurationSupport#configureMessageConverters
方法未注册http
消息转换器时,调用WebMvcConfigurationSupport#addDefaultHttpMessageConverters
方法添加默认的http
消息转换器,这时就会注册MappingJackson2HttpMessageConverter
。
首先自定义消息转换器:JsonHttpMessageConverter
。
@Slf4j
public class JsonHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
// 支持的MediaType
private final static List<MediaType> SUPPOR_MEDIA_TYPE;
static {
SUPPOR_MEDIA_TYPE = Arrays.asList(MediaType.APPLICATION_JSON);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return SUPPOR_MEDIA_TYPE;
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return super.canRead(mediaType);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return super.canRead(mediaType);
}
@Override
protected void writeInternal(Object object, Type type, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
// 实现序列化
MediaType contentType = httpOutputMessage.getHeaders().getContentType();
JsonEncoding encoding = this.getJsonEncoding(contentType);
String json = JsonUtils.toJsonString(object);
OutputStream outputStream = httpOutputMessage.getBody();
outputStream.write(json.getBytes(encoding.getJavaName()));
outputStream.flush();
}
@Override
public Object read(Type type, Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
// 实现反序列化
InputStream inputStream = httpInputMessage.getBody();
return JsonUtils.fromJson(inputStream, type);
}
}
如上代码所示,自定义的消息转换器支持转换MediaType.APPLICATION_JSON
类型的消息,在read
方法中调用自己封装的JsonUtils
反序列化消息,在writeInternal
方法(由父类write
方法调用)中调用自己封装的JsonUtils
序列化消息,并将消息写入到输出流。
最后只需编写继承WebMvcConfigurationSupport
的配置类,重写configureMessageConverters
方法,注册自定义的JsonHttpMessageConverter
。
@Configuration
@Slf4j
public class MessageConverterConfiguration extends WebMvcConfigurationSupport {
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters){
converters.add(new JsonHttpMessageConverter());
}
}
需要注意,如果重写configureMessageConverters
方法只注册自定义JsonHttpMessageConverter
,将会导致Content-Type
除application/json
之外的消息都无法处理。
适配spring-boot-actuator框架
使用自定义的消息转换器必然也要准备好面对可能出现的一些问题,例如。
运维要求应用需要提供一个心跳接口,以配置kubernetes
在pod
重启时,使用心跳接口判断新的pod
是否启动成功,以此为依据停止旧的pod
。刚好,spring-boot-actuator
框架提供了心跳接口,然而,接口却一直抛出如下异常。
异常给的信息也很明确,找不到可以转换当前Content-Type
的消息转化器。
根据异常栈消息,找到抛出异常的地方,下断点调试后得出如下图所示的信息。
从图中可以看出,/actuator/health
心跳接口响应的http
数据包,请求头的Content-Type
并非application/json
,而是application/vnd.spring-boot.actuator.v3+json
。
所以解决办法就是修改我们自定义的消息转换器,让消息转换器支持application/vnd.spring-boot.actuator.v3+json
类型。
public class JsonHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
private final static List<MediaType> SUPPOR_MEDIA_TYPE;
static {
SUPPOR_MEDIA_TYPE = Arrays.asList(
MediaType.APPLICATION_JSON,
// actuator框架使用
MediaType.valueOf("application/vnd.spring-boot.actuator.v3+json")
);
}
}
Spring MVC
多处使用组合模式+策略模式,为框架提供了非常好扩展性,例如本文提到的:
-
HttpMessageConverter(策略接口:消息转换器)
:Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. -
HandlerMethodReturnValueHandler(策略接口:方法返回值处理器)
:Strategy interface to handle the value returned from the invocation of a handler method . -
HandlerMethodArgumentResolver(策略接口:方法参数解析器)
:Strategy interface for resolving method parameters into argument values in the context of a given request.
如果觉得这篇内容对你有帮助,欢迎点赞和转发!
[Java艺术] 微信号:javaskill
一个只推送原创文章的技术公众号,分享Java后端相关技术。
- 《微信小程序七日谈》- 第六天:小程序devtool隐藏的秘密
- boi剖析 - 基于webpack的css sprites实现方案
- 深入JDK源码之ThreadLocal类
- 独家 | 一文读懂TensorFlow基础
- Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案
- RPC原理及实现
- RMI原理及实现
- webpack多页面开发与懒加载hash解决方案
- 前后端分离和模块化-58到家微信首页重构之路
- Node.js建站笔记-使用react和react-router取代Backbone
- 协同过滤推荐算法Java代码实现
- Nginx模块之Filter解析
- Nodejs建站笔记-注册登录流程的简单实现
- 前端工程化-构建
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- jdk1.8 自带的Base64加密与解密
- 缓冲字节输入流BufferedInputStream
- 在页面上写一个验证码
- 缓冲字节输出流BufferedOutputStream
- EL表达式获取简单数据与复杂数据(调用类)+EL表达式获取Map集合与list集合数据
- 用缓冲字节流,复制一个照片
- 09小结:封装结果集或实体类时,有多个对象的解决方法
- 序列化与反序列化Serializable,Externalizable
- response.getWriter().write()和 response.getWriter().print()的区别:
- java使用TCP,由客户端向服务端传输图片,(电脑与电脑)或(同一台电脑)
- 使用druid
- UDP实现多人聊天室
- 请求(doFilter)与响应乱码(BaseController+自定义注解@ContentType(““))集中处理
- 反射:Reflect(获取类对象三种方法)
- getParameterMap()返回参数需要对应实体类类型,否则收不到----打卡