Jackson 2.x 系列【24】Spring Web 集成

有道无术,术尚可求,有术无道,止于术。

本系列Jackson 版本 2.17.0

源码地址:https://gitee.com/pearl-organization/study-jaskson-demo

文章目录

    • 1. 前言
    • 2. Spring Web
    • 3. Jackson2ObjectMapperBuilder
    • 4. Jackson2ObjectMapperFactoryBean
    • 5. MappingJackson2HttpMessageConverter

1. 前言

Spring生态使用Jackson作为默认的JSON处理框架,这些年随着Spring的大发异彩和Jackson本身的优越特性,已成为世界上最流行的JSON库。

接下来,本系列会讲解Spring MVCSpring Boot中如何使用Jackson,以及Spring对其进行扩展增强的相关源码,所以需要读者有一定的SpringSpring Boot开发经验。

2. Spring Web

Spring MVC对于后端开发人员来说已经很熟悉了,它是一个构建在Servlet之上的一个Web框架,而Spring WebSpring MVC的基础模块(还有一个Spring Flux ),它们同属于spring-framework框架。

Web框架在处理HTTP请求响应时,需要将请求报文反序列化为Java对象进行接收处理,在响应阶段,需要将Java对象反序列化为报文写出,所以需要依赖JSON框架去处理。

Spring Web中可以看到引入了JacksonGJson

在这里插入图片描述
spring-web模块的org.springframework.http.converter.json包下,可以看到Json集成的代码:

在这里插入图片描述

集成Jackson的主要有:

  • Jackson2ObjectMapperBuilderObjectMapper构建器
  • Jackson2ObjectMapperFactoryBeanFactoryBean创建和管理ObjectMapper对象
  • MappingJackson2HttpMessageConverter:基于Jackson的消息转换器
  • SpringHandlerInstantiatorSpring容器创建Jackson组件,例如JsonSerializerJsonDeserializerKeyDeserializer
  • JacksonModulesRuntimeHintsSpring AOT所需的RuntimeHints(运行时提示)

3. Jackson2ObjectMapperBuilder

Jackson2ObjectMapperBuilder从名字上看已经很好理解,它是一个JacksonObjectMapper构建器,提供了Fluent API来定制ObjectMapper的默认属性并构建实例。

在之前我们都是通过new的方式创建ObjectMapper实例,现在可以使用构建者模式,这也是Spring自身使用和推荐使用的方式。

它的构造方法是public的,说明可以直接new创建Jackson2ObjectMapperBuilder

    public Jackson2ObjectMapperBuilder() {}

提供了多个创建不同数据类型支持的静态方法:

	// 构建 Jackson2ObjectMapperBuilderpublic static Jackson2ObjectMapperBuilder json() {return new Jackson2ObjectMapperBuilder();}// 构建 Jackson2ObjectMapperBuilder,并设置需要创建 XmlMapper,需要引入`jackson-dataformat-xml`模块,用于支持`XML`数据格式public static Jackson2ObjectMapperBuilder xml() {return new Jackson2ObjectMapperBuilder().createXmlMapper(true);}// 指定 JsonFactory-》SmileFactory,需要引入`jackson-dataformat-smile`模块,用于支持`Smile`数据格式public static Jackson2ObjectMapperBuilder smile() {return new Jackson2ObjectMapperBuilder().factory(new SmileFactoryInitializer().create());}// 指定 JsonFactory-》CBORFactory-》需要引入`jackson-dataformat-cbor`模块,用于支持`CBOR`数据格式public static Jackson2ObjectMapperBuilder cbor() {return new Jackson2ObjectMapperBuilder().factory(new CborFactoryInitializer().create());}

一般场景中,使用json()创建即可:

ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();

此外还提供了很多方法,例如自定义序列化/反序列化器、模块注册、启用/禁用特征、 混合注解等等,和ObjectMapper 本身的方法大多类似,这里就不一一赘述了。

	// 配置自定义序列化器(多个)public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) {for (JsonSerializer<?> serializer : serializers) {Class<?> handledType = serializer.handledType();if (handledType == null || handledType == Object.class) {throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName());}this.serializers.put(serializer.handledType(), serializer);}return this;}// 模块注册public Jackson2ObjectMapperBuilder modules(Module... modules) {return modules(Arrays.asList(modules));}/*** 启用特征** @see com.fasterxml.jackson.core.JsonParser.Feature* @see com.fasterxml.jackson.core.JsonGenerator.Feature* @see com.fasterxml.jackson.databind.SerializationFeature* @see com.fasterxml.jackson.databind.DeserializationFeature* @see com.fasterxml.jackson.databind.MapperFeature*/public Jackson2ObjectMapperBuilder featuresToEnable(Object... featuresToEnable) {for (Object feature : featuresToEnable) {this.features.put(feature, Boolean.TRUE);}return this;}

需要重点关注的方法有buildconfigurebuild方法用于构建一个ObjectMapper实例,每次调用都会返回一个新的对象,对于处理不同JSON格式或需要不同序列化/反序列化行为的场景,可以构建新的实例来处理。

	@SuppressWarnings("unchecked")public <T extends ObjectMapper> T build() {// 创建实例ObjectMapper mapper;if (this.createXmlMapper) {// 需要创建XmlMapper,则使用 XmlFactorymapper = (this.defaultUseWrapper != null ?new XmlObjectMapperInitializer().create(this.defaultUseWrapper, this.factory) :new XmlObjectMapperInitializer().create(this.factory));} else {// 不需要创建XmlMapper,则使用指定的工厂mapper = (this.factory != null ? new ObjectMapper(this.factory) : new ObjectMapper());}// 配置configure(mapper);return (T) mapper;}

configure方法用于将成员属性都设置给已存在的ObjectMapper实例:

	public void configure(ObjectMapper objectMapper) {Assert.notNull(objectMapper, "ObjectMapper must not be null");// 注册模块MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();if (this.findModulesViaServiceLoader) {ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister));} else if (this.findWellKnownModules : modulesToRegister.values()) {modules.addAll(nestedModules);}objectMapper.registerModules(modules);// 设置时间日期格式化if (this.dateFormat != null) {objectMapper.setDateFormat(this.dateFormat);}// 设置 Localeif (this.locale != null) {objectMapper.setLocale(this.locale);}// 设置 TimeZone	if (this.timeZone != null) {objectMapper.setTimeZone(this.timeZone);}// 设置 注解解析器if (this.annotationIntrospector != null) {objectMapper.setAnnotationIntrospector(this.annotationIntrospector);}// 设置属性名称策略if (this.propertyNamingStrategy != null) {objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);}// 省略其他.........if (this.configurer != null) {this.configurer.accept(objectMapper);}}

4. Jackson2ObjectMapperFactoryBean

Jackson2ObjectMapperFactoryBean提供了基于Spring框架FactoryBean机制创建和配置ObjectMapper实例。

示例如下:

@Configuration
public class MvcConfig {@Beanpublic Jackson2ObjectMapperFactoryBean jacksonObjectMapper() {Jackson2ObjectMapperFactoryBean factoryBean = new Jackson2ObjectMapperFactoryBean();factoryBean.setIndentOutput(true);factoryBean.setSimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 还可以设置其他属性,如自定义的序列化器、反序列化器等return factoryBean;}
}

5. MappingJackson2HttpMessageConverter

MappingJackson2HttpMessageConverter即基于Jackson2的消息转换器实现,其本身并没有多少代码,读写能力来自于父类。

继承关系如下:

  • HttpMessageConverter
    • GenericHttpMessageConverter
      • AbstractGenericHttpMessageConverter
        • AbstractJackson2HttpMessageConverter
          • MappingJackson2HttpMessageConverter

顶级接口HttpMessageConverterSpring Web定义的一个HTTP消息转换器。负责将请求和响应的数据从Java对象转换为HTTP协议所需的格式,或者将HTTP协议中的数据转换为Java对象,是Spring框架中用于处理HTTP请求和响应数据转换的重要组件。

直接父类AbstractJackson2HttpMessageConverter定义了泛型为Object,其内部维护了多个ObjectMapper对象:

public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> // 默认的ObjectMapperprotected ObjectMapper defaultObjectMapper;@Nullable// registerObjectMappersForType 方法注册的多个ObjectMapperprivate Map<Class<?>, Map<MediaType, ObjectMapper>> objectMapperRegistrations;

可以调用构造方法或者set方法设置默认的ObjectMapper

    protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {this.defaultObjectMapper = objectMapper;DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();prettyPrinter.indentObjectsWith(new DefaultIndenter("  ", "\ndata:"));this.ssePrettyPrinter = prettyPrinter;}protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {this(objectMapper);this.setSupportedMediaTypes(Collections.singletonList(supportedMediaType));}protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {this(objectMapper);this.setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));}public void setObjectMapper(ObjectMapper objectMapper) {Assert.notNull(objectMapper, "ObjectMapper must not be null");this.defaultObjectMapper = objectMapper;this.configurePrettyPrint();}

重写了父类的canReadcanWrite方法,例如canRead,首先调用父类的 canRead 方法,是否支持当前 MediaType,然后查询并判断ObjectMapper是否支持反序列化当前类型:

    public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {// 调用父类的 canRead 方法,是否支持当前 MediaTypeif (!this.canRead(mediaType)) {return false;} else {// 根据JAVA Type 类型,转换为 Jackson 的 JavaType JavaType javaType = this.getJavaType(type, contextClass);// 查询一个可用的 ObjectMapper ObjectMapper objectMapper = this.selectObjectMapper(javaType.getRawClass(), mediaType);if (objectMapper == null) {return false;} else {// ObjectMapper  是否可反序列化当前类型 AtomicReference<Throwable> causeRef = new AtomicReference();if (objectMapper.canDeserialize(javaType, causeRef)) {return true;} else {this.logWarningIfNecessary(javaType, (Throwable)causeRef.get());return false;}}}}

最重要的是真正实现了转换器最核心的读写方法,例如read方法中并不是直接通过ObjectMapper进行读操作,而是通过ObjectMapper创建了底层的ObjectReader 去执行读操作。

	// 将 HTTP 输入消息,转换为 JAVA 对象public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {JavaType javaType = this.getJavaType(type, contextClass);return this.readJavaType(javaType, inputMessage);}private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {// 获取 Content-TypeMediaType contentType = inputMessage.getHeaders().getContentType();// 获取 Charset Charset charset = this.getCharset(contentType);// 查询可用的 ObjectMapper ObjectMapper objectMapper = this.selectObjectMapper(javaType.getRawClass(), contentType);Assert.state(objectMapper != null, () -> {return "No ObjectMapper for " + javaType;});boolean isUnicode = ENCODINGS.containsKey(charset.name()) || "UTF-16".equals(charset.name()) || "UTF-32".equals(charset.name());try {// 获取输入流 InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());if (inputMessage instanceof MappingJacksonInputMessage) {// 支持 Jackson 视图 MappingJacksonInputMessage mappingJacksonInputMessage = (MappingJacksonInputMessage)inputMessage;Class<?> deserializationView = mappingJacksonInputMessage.getDeserializationView();if (deserializationView != null) {ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);objectReader = this.customizeReader(objectReader, javaType);if (isUnicode) {return objectReader.readValue(inputStream);}Reader reader = new InputStreamReader(inputStream, charset);return objectReader.readValue(reader);}}// 使用 `ObjectMapper` 底层的`ObjectReader`读取ObjectReader objectReader = objectMapper.reader().forType(javaType);objectReader = this.customizeReader(objectReader, javaType);if (isUnicode) {return objectReader.readValue(inputStream);} else {Reader reader = new InputStreamReader(inputStream, charset);return objectReader.readValue(reader);}} catch (InvalidDefinitionException var12) {throw new HttpMessageConversionException("Type definition error: " + var12.getType(), var12);} catch (JsonProcessingException var13) {throw new HttpMessageNotReadableException("JSON parse error: " + var13.getOriginalMessage(), var13, inputMessage);}}

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

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

相关文章

比例控制器H5773282、H8135950、H3390627、H6079948

名称&#xff1a;BEUEC数字比例放大器、伺服比例控制器、伺服比例阀放大板&#xff0c;订货代号&#xff1a;H5773282、H8135950、H3390627、H6079948、H6108848、H6700353、H8851035、H1688388、H9549313、H3264103、H1182967&#xff0c;输入指令可选10V、4-20mA&#xff0c;…

Session缓存、Hibernate处理对象的状态了解

Session接口 Session接口是Hibernate向应用程序提供的操纵数据库的最主要的接口&#xff0c;它提供了基本的保存&#xff0c;更新&#xff0c;删除和查询的方法。 Session是有一个缓存, 又叫Hibernate的一级缓存 session缓存是由一系列的Java集合构成的。当一个对象被加入到…

[大模型]Atom-7B-Chat 接入langchain搭建知识库助手

Atom-7B-Chat 接入langchain搭建知识库助手 环境准备 在autodl平台中租一个3090等24G显存的显卡机器&#xff0c;如下图所示镜像选择PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 接下来打开刚刚租用服务器的JupyterLab&#xff0c;并且打开其中的终端开始环境配置…

Linux 网络测速

1.开发背景 网络测速&#xff0c;为了测试开发板的网络速度是否达标的通用测试方法 2.开发需求 搭建 iperf3 &#xff0c;在 ubuntu 下安装服务端&#xff0c;在板卡上安装客户端&#xff0c;服务端和客户端互发 3.开发环境 ubuntu20.04 嵌入式开发板&#xff08;debian 千…

LeetCode_丑数

题目&#xff1a; 题解&#xff1a; 由题&#xff0c;我们知道丑数大于0&#xff0c;丑数都可以写成2*2*...*2*3*3...*3*5*5...*5&#xff0c;有了这个基础就很好写代码了。 用三个while循环将前面的2 3 5全部除掉如果这个数是丑数&#xff0c;最后n是等于1的&#xff0c;反之…

C++_第五周做题总结_构造与析构

id:31 A.Point&#xff08;类与构造&#xff09; 题目描述 下面是一个平面上的点的类定义&#xff0c;请在类外实现它的所有方法&#xff0c;并生成点测试它。 class Point {double x, y; public:Point(); // 缺省构造函数&#xff0c;给x,y分别赋值为0Point(double x_value…

nvm node.js的安装

说明&#xff1a;部分但不全面的记录 因为过程中没有截图&#xff0c;仅用于自己的学习与总结 过程中借鉴的优秀博客 可以参考 1,npm install 或者npm init vuelatest报错 2&#xff0c;了解后 发现是nvm使用的版本较低&#xff0c;于是涉及nvm卸载 重新下载最新版本的nvm 2…

激光雷达初识

一、实车激光雷达 一般在车顶位置: 二、激光雷达组成 激光收发器模块:发射激光器VCSEL+接收模块采用了SiPM(硅基光电倍增管)或者APD,一个激光器发生失效的情况,其他仍可正常工作 扫描模块:水平视场和的垂直视场的扫描,128个阵列的VCSEL激光器负责 信号处理模块:信号处…

深入理解 C++ 中的 KeyFrame 和 KeyFrame*:对象与指针的选择与管理

本文详细讨论了在 C 编程中 KeyFrame 类及其指针 KeyFrame* 的用法、区别与联系。通过探索两者的内存管理、生命周期及使用场景&#xff0c;本文旨在帮助开发者更好地理解何时以及如何选择使用对象或指针&#xff0c;从而提高代码的效率和安全性。 在 C 中&#xff0c;KeyFrame…

【电控笔记2.4】前馈技术

2.4前馈技术 前馈可以减轻控制器的负担

画板探秘系列:创意画笔第一期

前言 我目前在维护一款功能强大的开源创意画板。这个画板集成了多种创意画笔&#xff0c;可以让用户体验到全新的绘画效果。无论是在移动端还是PC端&#xff0c;都能享受到较好的交互体验和效果展示。并且此项目拥有许多强大的辅助绘画功能&#xff0c;包括但不限于前进后退、…

【SQL】DISTINCT GROUP BY

找到所有办公室里的所有角色&#xff08;包含没有雇员的&#xff09;,并做唯一输出(DISTINCT) 用DISTINCT : SELECT DISTINCT B.Building_name,E.Role FROM Buildings B LEFT JOIN Employees EON B.Building_name E.Building需要找到的结果&#xff1a;所有办公室名字&#…

iOS framework 相关知识

苹果官方目前不允许开发者使用动态库&#xff0c;所以下面只说明相关静态库使用知识&#xff0c;目前只做简单的记录&#xff0c;后续会做完整的整理 如何在同一个WorkSpace里面创建一个主工程和多个子framework 首先创键一个工程&#xff0c;然后创建一个workspace&#xf…

线程通信-java

线程通信 当多个线程操作共享多资源时&#xff0c;线程间通过某种方式相互告知自己的状态&#xff0c;以相互协调&#xff0c; 并避免无效的资源争夺。 线程通信的常见模型&#xff08;生产者与消费者模型&#xff09; 生产者线程负责生产数据 消费者线程负责消费生产者生…

表单自定义系统源码:自主创建表单 带完整的安装代码包以及搭建教程

在当今信息化社会&#xff0c;表单作为一种常见的数据收集工具&#xff0c;广泛应用于各类网站和系统中。然而&#xff0c;传统的表单系统往往功能单一&#xff0c;缺乏灵活性&#xff0c;难以满足用户多样化的需求。下面&#xff0c;小编给大家分享一款表单自定义系统源码&…

YOLOv8改进 添加大核卷积序列注意力机制LSK

一、Large Separable Kernel Attention论文 论文地址:2309.01439.pdf (arxiv.org) 二、Large Separable Kernel Attention注意力结构 LSK通过使用大型可分离卷积核来提升注意力机制的效果。在传统的注意力机制中,常用的是小型卷积核,如1x1卷积,来计算注意力权重和特征表示…

微信小程序英文版:实现一键切换中英双语版(已组件化)

已经重新优化代码做成了组件&#xff0c;需要可自取&#xff1a;https://github.com/CrystalCAI11/wechat-language-compoment 所有操作都打包在组件里不需要在额外的地方添加代码&#xff0c;直接在你需要的页面里导入组件&#xff0c;再在对应页面的onLoad()里set文本就行了。…

【嵌入式】嵌入式开发中常见的面试题(持续更新中)

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

【位运算】Leetcode 消失的两个数字

题目解析 面试题 17.19. 消失的两个数字 算法讲解 我们将这两个数组异或在一起&#xff0c;最后的结果就是a ^ b(缺失的两个数字)的结果&#xff0c;这两个缺失的数字一定是不相同的&#xff0c;所以我们就寻找他们第一个比特位是1的那个位置&#xff0c;异或的原理是&#xf…

半导体存储电路知识点总结

目录 一、SR锁存器 1.SR锁存器的概念 2.作用 二、电平触发器&#xff08;Flip-Flop&#xff09; 1.时钟信号 2.电平触发的触发器电路结构 3.带异步置位复位的电平触发器 三、边沿触发器 1.特点 2.两个D触发器组成的边沿触发D触发器 3.CMOS边沿触发D触发器的典型电路 …