spring:基于SimpleModule实现动态管理jackson的序列化器(JsonSerializer)和反序列化器(JsonDeserializer)

Module

jackson的(com.fasterxml.jackson.databind.Module)设计作为一个扩展的接口,可以注册到ObjectMapper实例(ObjectMapper.registerModule),为默认ObjectMapper实例提供功能扩展;比如用于定义为数据类型指定序列化和反序列化。
jackson为Module接口提供了一个默认的简单实现(com.fasterxml.jackson.databind.module.SimpleModule),SimpleModule已经很好用了,一般情况下可以直接拿来用。
网上关于jackson使用SimpleModule的例子,差不多都是下面这样的:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
JsonSerializer<?> serializer=.....
/** 向 Module增加序列化器 */
module.addSerializer(serializer);
/** 将 Module注册到 ObjectMapper实例 */
objectMapper.registerModule(module);

这些例子,给我一个错觉,以为必须要在构造ObjectMapper实例时就要将定义了指定类型的序列化器的Module实例注册到 ObjectMapper实例才能生效。

SimpleModule

最近的工作中,我需要实现能在spring环境下,在服务运行时根据服务方法的不同动态修改数据类型的序列化器。这就让我不得不仔细了解了SimpleModule的实现。
才明白,jackson的Module机制是可以支持动态修改Module实例的,并且只要将Module注册到ObjectMapper,再修改Module实例的结果会同步到ObjectMapper实例。
还以上面的增加序列化器的过程为例,
SimpleModule中管理序列化器的字段为_serializers,类型为SimpleSerializers
ObjectMapper.registerModule注册Module的过滤其实就是将_serializers字段的实例增加到
ObjectMapperSerializerFactory实例(_serializerFactory字段)
参见下面ObjectMapper.registerModule方法的实现代码片段

public void addSerializers(Serializers s) {_serializerFactory = _serializerFactory.withAdditionalSerializers(s);
}

所以可以先将SimpleModule注册到ObjectMapper,再调用SimpleModule.addSerializer方法也是同样可以将JsonSerializer实例传递给已经注册的ObjectMapper实例的。

(一)初步方案

所以一个大概的实现方案就有了:
大概致思路就是先向ObjectMapper实例注册一个空的SimpleModule实例,
后续就可以如直接向SimpleModule实例,增加序列化器了。好简单。

ExampleOfSimpleModule.java

@Component
public class ExampleOfSimpleModule {/**  * [自动注入]spring环境下的ObjectMapper实例*/@Autowired	private ObjectMapper objectMapper;private final SimpleModule module = new SimpleModule();@PostConstructvoid init() {/** 先将空SimpleModule实例的注册到系统的ObjectMapper实例 */objectMapper.registerModule(module);}/*** 向 {@link #module}增加过序列化器* @param serializer*/public void addSerializer(JsonSerializer<?> serializer) {if(null != serializer) {module.addSerializer(serializer);}}
}

然而实际运行发现这样根本无效。ObjectMapper在序列化时并没有使用我指定的序列化器。
再仔细看SimpleModule的源码,发现SimpleModule的默认构造方法构造的实例 _serializers为null,
SimpleModule.setupModule方法执行时如果_serializers为null,就不会执行向ObjectMapper增加序列化器的逻辑

SimpleModule.setupModule方法代码片段:

public void setupModule(SetupContext context)
{if (_serializers != null) {context.addSerializers(_serializers);}/..........///
}

(二)改进方案

所以上面方案示例代码ExampleOfSimpleModule的void init()方法代码要改一下:
调用SimpleModule.setSerializers方法为_serializers定义一个非null的SimpleSerializers实例

	@PostConstructvoid init() {/*** SimpleModule中的 _serializers字段默认为空,如果不事先调用setter方法指定非空实例,* 则执行 ObjectMapper.registerModule方法无效,* 后续动态增加的序列化和反序列化器也无效*/module.setSerializers( new SimpleSerializers());objectMapper.registerModule(module);}

(三)改进方案

经过改进,ObjectMapper注册Module无效的问题总算是解决了,但实际服务运行时,还是没有使用我指定的序列化器。
这次要深入看ObjectMapper的源码了:
ObjectMapper获取类型的序列化器是通过_serializerProvider字段的SerializerProvider实例的findTypedValueSerializer方法来获取的:

如下是SerializerProvider.findTypedValueSerializer(Class<?> valueType, boolean cache, BeanProperty property)方法代码片段:

public JsonSerializer<Object> findTypedValueSerializer(Class<?> valueType,boolean cache, BeanProperty property)throws JsonMappingException
{// Two-phase lookups; local non-shared cache, then shared:JsonSerializer<Object> ser = _knownSerializers.typedValueSerializer(valueType);if (ser != null) {return ser;}......
}

SerializerProvider有个final_knownSerializers字段,它就类似一个缓存。
findTypedValueSerializer首先会从_knownSerializers字段查找已知的序列化器,如果找到就直接返回,根本不会从SerializerFactory获取(前面我们知道Module中定义的序列化器是保存到了SerializerFactory对象)
进一步在SerializerProvider的构造方法中找到了_knownSerializers字段的来源,它来源于_serializerCache字段,果然看名字就知道这_serializerCache字段也是一个类型为SerializerCache的缓存,

SerializerProvider构造方法代码片段

    protected SerializerProvider(SerializerProvider src,SerializationConfig config, SerializerFactory f){_serializerFactory = f;_config = config;_serializerCache = src._serializerCache;//。。。。/_knownSerializers = _serializerCache.getReadOnlyLookupMap();}

SerializerCache缓存是可以清除的
如下为SerializerCache清除缓存的方法

public synchronized void flush() {_sharedMap.clear();
}

如果我增加了序列化器后,能够序列化器清除缓存,那么findTypedValueSerializer方法的逻辑就会继续往下走,就应该会去查找我在SimpleModule中定义的序列化器了。
进一步查看了DefaultSerializerProvider的源码,
哎呀,真是瞌睡时遇到枕头,
它果然有一个方法flushCachedSerializers()可以清除_serializerCache字段缓存
如下:

public void flushCachedSerializers() {_serializerCache.flush();}

现在问题应该就解决了,就是在向SimpleModule增加序列化器之后,就调用DefaultSerializerProvider.flushCachedSerializers()方法清除缓存,这样增加的序列化器应该就能生效了。
所以上面方案示例代码ExampleOfSimpleModule的addSerializer法代码要改一下:

	public void addSerializer(JsonSerializer<?> serializer) {if(null != serializer) {module.addSerializer(serializer);/** 清除 DefaultSerializerProvider中的序列化器缓存,否则增加的过滤器不一定能生效  */DefaultSerializerProvider prov= (DefaultSerializerProvider) objectMapper.getSerializerProvider();prov.flushCachedSerializers();}}

重新运行测试,果然生效。

addDeserializer

有了上面的经验,我又如法炮制,研究了增加反序列化器的方法。发现
DeserializationContext同样有一个名为_cache的反序列化器缓存(DeserializerCache)字段。
DeserializerCacheflushCachedDeserializers()方法可以清除缓存。但问题是DeserializationContext却没有方法可以调用_cache字段的这个方法来执行缓存清除动作,而_cache为保护字段(proteced)不能直接访问。

看来jackson的序列化和反序列化模块应该是不同的作者维护的。

我当前使用的jackson版本是2.12.5,我以为高版本会增加这样的方法,但是我查了最新版本,仍然没有,好吧,只能另外想办法了。
于是我在com.fasterxml.jackson.databind包下写了类DeserializeContextCacheCleaner ,继承DeserializationContext。只为调用_cache.flushCachedDeserializers()

DeserializeContextCacheCleaner.java

package com.fasterxml.jackson.databind;/*** only for clear cache in {@link DeserializationContext}* @author guyadong**/
@SuppressWarnings("serial")
public abstract class DeserializeContextCacheCleaner extends DeserializationContext{private DeserializeContextCacheCleaner(DeserializationContext src) {super(src);}/*** 清除{@link DeserializationContext#_cache}字段缓存*/public static void clearCache(DeserializationContext src) {if(null != src) {src._cache.flushCachedDeserializers();}}	
}

有了DeserializeContextCacheCleaner支持,向SimpleModule增加反序列化器就简单了:

	/*** 向 {@link #module}增加过反序列化器* @param deserializer*/@SuppressWarnings("unchecked")public void addDeserializer(JsonDeserializer<?> deserializer) {if(null != deserializer) {module.addDeserializer((Class<Object>)(deserializer).handledType(), deserializer);/** 清除 DeserializationContext中的反序列化器缓存,否则增加的过滤器不一定能生效  */DeserializeContextCacheCleaner.clearCache(objectMapper.getDeserializationContext());}}

完整方案:

好了上面零零碎碎写了好多代码片段,现在把它们整理出来就是完整的动态管理jackson的序列化器和反序列化器的实现方案示例代码:
ExampleOfSimpleModule.java

import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.DeserializeContextCacheCleaner;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;/*** 动态管理序列化器和反序列化器实现* @author guyadong*/
@Component
public class ExampleOfSimpleModule {/**  * [自动注入]spring环境下的ObjectMapper实例*/@Autowired	private ObjectMapper objectMapper;private final SimpleModule module = new SimpleModule();@PostConstructvoid init() {/*** SimpleModule中的 _serializers和 _deserializers字段默认为空,如果不事先调用setter方法指定非空实例,* 则执行 ObjectMapper.registerModule方法无效,* 后续动态增加的序列化和反序列化器也无效*/module.setSerializers( new SimpleSerializers());module.setDeserializers(new SimpleDeserializers());objectMapper.registerModule(module);}/*** 向 {@link #module}增加过反序列化器* @param deserializer*/@SuppressWarnings("unchecked")public void addDeserializer(JsonDeserializer<?> deserializer) {if(null != deserializer) {module.addDeserializer((Class<Object>)(deserializer).handledType(), deserializer);/** 清除 DeserializationContext中的反序列化器缓存,否则增加的过滤器不一定能生效  */DeserializeContextCacheCleaner.clearCache(objectMapper.getDeserializationContext());}}/*** 向 {@link #module}增加过序列化器* @param serializer*/public void addSerializer(JsonSerializer<?> serializer) {if(null != serializer) {module.addSerializer(serializer);/** 清除 DefaultSerializerProvider中的序列化器缓存,否则增加的过滤器不一定能生效  */DefaultSerializerProvider prov= (DefaultSerializerProvider) objectMapper.getSerializerProvider();prov.flushCachedSerializers();}}
}

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

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

相关文章

MySQL: 索引与事务

文章目录 1. 索引 (Index)1.1 概念1.2 作用1.3 使用场景1.4 索引的使用1.5 索引的使用案例 (不要轻易尝试)1.6 索引背后的数据结构1.7 重点总结 2.事务2.1 为什么要使用事务2.2 事务的概念2.3 事务的使用2.4 对事务的理解2.5 事务的基本特性 1. 索引 (Index) 1.1 概念 索引是…

深入STL之 栈与队列:数据结构探索之旅

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;模拟实现list与迭代器 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀stack和queue &#x1f4…

探索软件工程师在新能源汽车研发中的角色与贡献

随着全球对可持续发展的关注不断增加&#xff0c;新能源汽车的研发与应用成为了汽车行业的一个重要方向。作为软件工程师&#xff0c;参与新能源汽车研发不仅能够推动科技创新&#xff0c;还能为环保事业贡献力量。本文将深入探讨软件工程师在新能源汽车研发中的具体贡献、所需…

C#操作MySQL从入门到精通(20)——更新数据

前言: 谈到数据库,大家最容易脱口而出的就是增删改查,本文所说的更新数据就是增删改查的改,改变数据的意思。 本文测试使用的数据库如下: 1、更新一列 所谓更新一列的意思就是只更改一列数据,并且通常要使用where条件,因为不加这个条件的话会导致将所有行的数据进行…

数据可视化Python实现超详解【数据分析】

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

后端服务器启动访问

VisualStudioCode(VSCode) 服务器启动 浏览器中测试访问 http://localhost:3000

图的遍历详解

目录 采用DFS遍历图 采用BFS遍历图 采用DFS遍历图 &#xff08;1&#xff09;邻接矩阵版 int n,G[maxn][maxn]; bool vis[maxn]{false}; void dfs(int u,int depth){vis[u]true;for(int v0;v<n;v){if(vis[v]false&&G[u][v]!INF){dfs(v,depth1);}} } void dfstra…

算法笔记1-高精度模板(加减乘除)个人模板

目录 加法 减法 乘法 ​编辑 除法 加法 #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <queue>using namespace std;typedef pair<int,int> PII;const int N 1e5 10;int n; int a[N],…

ROS学习记录:C++节点发布自定义地图

前言 ROS栅格地图格式 在了解了ROS地图消息包的数据结构后(链接在上)&#xff0c;本文将编写一个节点&#xff0c;发布地图消息包&#xff0c;看看在RViz中显示是什么效果。 一、准备 1、为了简单起见&#xff0c;发布一个两行四列的地图 2、为了便于观测&#xff0c;只对地…

机器学习:如何在Python中实现决策树分类?

如何在Python中实现决策树分类&#xff1f; 在机器学习领域&#xff0c;决策树算法是一种常用且高效的分类与回归方法。它不仅易于理解和解释&#xff0c;还能处理数值型和分类型数据。本文将带你深入探索Python中的决策树算法&#xff0c;理解其基本原理&#xff0c;并通过代…

SmartEDA VS Multisim/Proteus:电子设计江湖,谁主沉浮?

在电子设计的江湖里&#xff0c;SmartEDA、Multisim和Proteus无疑是几大门派&#xff0c;各自拥有独特的武功秘籍和门派特色。今天&#xff0c;我们就来一场巅峰对决&#xff0c;看看这些电子设计软件究竟谁能笑傲江湖&#xff0c;成为电子设计界的霸主&#xff01; 一、门派起…

Linux:通过线程互斥同步实现基于BlockingQueue的生产消费者模型

一、总体调度&#xff1a;主函数Main.cc #include "BlockQueue.hpp" #include "Thread.hpp" #include <string> #include <vector> #include <functional> #include <unistd.h> #include <ctime>using namespace ThreadMod…

Seq2seq、编码器解码器神经网络

目录 一、Seq2seq 简介二、编码器三、解码器四、编码器-解码器的训练 遇到看不明白的地方&#xff0c;欢迎在评论中留言呐&#xff0c;一起讨论&#xff0c;一起进步&#xff01; 需掌握的前提知识&#xff1a; LSTM、词嵌入 本文参考&#xff1a;【官方双语】编码、解码神经网…

PasteCluster组件介绍(一款让你的.Net服务快速支持集群部署的中间件)

前言 PasteCluster是由.NET6.0编写的集群中间件&#xff0c;先已开源: PasteCluster.Gitee 在实际开发中&#xff0c;如果一个服务(比如api)是否支持集群部署&#xff0c;其实是由开发决定的&#xff01; 举个栗子 我们知道缓存&#xff0c;可以分几种方式&#xff0c;最简单的…

tkinter菜单栏

tkinter菜单栏 菜单栏效果代码 菜单栏 在 Tkinter 中&#xff0c;Menu 组件用于创建菜单栏、下拉菜单和上下文菜单&#xff0c;是构建图形用户界面&#xff08;GUI&#xff09;应用程序的常见需求。 效果 代码 import tkinter as tk from tkinter import messagebox# 创建主…

DAMA学习笔记(一)-数据管理

1.引言 数据管理(Data Management) 是为了 交付、 控制、 保护 并 提升 数据和信息资产的 价值 , 在其整个生命周期中制订 计划、 制度、 规程和实践 活动, 并 执行 和 监督 的过程。 数据管理专业人员(Data Management Professional) 是指 从事数据管理各方面的工作…

MySQL与PostgreSQL关键对比三(索引类型)

目录 索引类型 B-tree 索引 Hash 索引 Full-text 索引 GiST 索引 GIN 索引 BRIN 索引 索引创建示例 MySQL PostgreSQL 结论 以下SQL语句的执行如果需要开发工具支持&#xff0c;可以尝试使用SQLynx或Navicat来执行。 MySQL和PostgreSQL在索引方面有许多相似之处&am…

【C#线程设计】2:backgroundWorker

实现&#xff1a; &#xff08;1&#xff09;.控件&#xff1a;group Box&#xff0c;text Box&#xff0c;check Box&#xff0c;label&#xff0c;botton&#xff0c;richtextbox 控件拉取见&#xff1a;https://blog.csdn.net/m0_74749240/article/details/139409510?spm1…

mingw如何制作动态库附python调用

1.mingw和msvc g -fpic HelloWorld.cpp -shared -o test.dllg -L . -ltest .\test.cpp 注意-L后面的.挨不挨着都行&#xff0c;-l不需要-ltest.dll&#xff0c;只需要-ltest 2.dll.cpp extern "C" {__declspec(dllexport) int __stdcall add(int a, int b) {return…

吴恩达2022机器学习专项课程C2W3:2.25 理解方差和偏差(诊断方差偏差正则化偏差方案)

目录 引言名词替代影响模型偏差和方差的因素1.多项式阶数2.正则化参数 判断是否有高偏差或高方差1.方法一&#xff1a;建立性能基准水平2.方法二&#xff1a;建立学习曲线 总结 引言 机器学习系统开发的典型流程是从一个想法开始&#xff0c;然后训练模型。初次训练的结果通常…