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() {
    }

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

	// 构建 Jackson2ObjectMapperBuilder
	public 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,则使用 XmlFactory
			mapper = (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);
		}
		// 设置 Locale
		if (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 {

    @Bean
    public 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> 
	// 默认的ObjectMapper
    protected ObjectMapper defaultObjectMapper;
    @Nullable
    // registerObjectMappersForType 方法注册的多个ObjectMapper
    private 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 方法,是否支持当前 MediaType
        if (!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-Type
        MediaType 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.mfbz.cn/a/559113.html

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

相关文章

探索传感器世界:类型与应用详解

传感器是一种能感知并测量特定物理量、化学量或其他参数&#xff0c;并将其转换为可供处理、记录或控制的电信号的装置。 物联网传感器设备种类繁多&#xff0c;以下是一些常见的类型&#xff1a; 一、 温度传感器 1、热电阻温度传感器&#xff1a;利用金属的电阻随温度变化的…

Java编程题 | 数组元素交换

大家可以关注一下专栏&#xff0c;方便大家需要的时候直接查找&#xff0c;专栏将持续更新~ 题目描述 编写一个Java程序&#xff0c;输入一个整数数组&#xff0c;将最大的元素与第一个元素交换&#xff0c;最小的元素与最后一个元素交换&#xff0c;然后输出修改后的数组…

PHP定时任务框架taskPHP3.0学习记录4宝塔面板bash定时任务(轮询指定json文件字段后确定是否执行、环境部署、执行日志、文件权限)

一 需求说明 宝塔面板中,读取指定 /www/wwwroot/lockdata/cron/webapp.json文件&#xff1b;配置定时任务脚本task.sh&#xff1b;当读取webapp.json中&#xff0c;如果cron_task1&#xff0c;则执行任务php start.php start命令行&#xff1b;完成命令后&#xff0c;执行cron…

Vue3基本功能介绍

文章目录 Vue3组件中的模板结构可以没有根标签div组合式APIRefReactive函数回顾Vue2响应式Vue3实现响应式对比reactive和refSetup注意点计算属性与监听computedWatchWatchEffectVue3生命周期自定义hook函数toRef其他组合APIshallowReactiveshallowRefreadonly和shallowOnlyToRa…

PostgreSql-Install

PostgreSql源码安装 一、源代码下载二、操作系统配置三、编译安装四、启动数据库五、相关命令 PostgreSQL是一个强大的 开源对象关系数据库系统&#xff0c;它使用并扩展了SQL语言&#xff0c;并结合了许多功能&#xff0c;可以安全地存储和扩展最复杂的数据工作负载。 一、源…

Targeted influence maximization in competitive social networks

abstract 利用口碑效应的广告对于推销产品是相当有效的。在过去的十年中&#xff0c;人们对营销中的影响力最大化问题进行了深入的研究。影响力最大化问题旨在将社交网络中的一小群人识别为种子&#xff0c;最终他们将引发网络中最大的影响力传播或产品采用。在网络营销的实际场…

HTML、CSS常用的vscode插件 +Css reset 和Normalize.css

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍HTML、CSS常用的vscode插件&#x1f34e;1 HTML 标签同步重命名 – Auto Re…

大型网站系统架构演化实例_7.使用NoSQL和搜索引擎

1.使用NoSQL和搜索引擎 随着网站业务越来越复杂&#xff0c;对数据存储和检索的需求也越来越复杂&#xff0c;网站需要采用一些非关系数据库技术如NoSQL和非数据库查询技术如搜索引擎。NoSQL和搜索引擎都是源自互联网的技术手段&#xff0c;对可伸缩的分布式特性具有更好的支持…

【代理模式】静态代理-简单例子

在Java中&#xff0c;静态代理是一种设计模式&#xff0c;它涉及到为一个对象提供一个代理以控制对这个对象的访问。静态代理在编译时就已经确定&#xff0c;代理类和被代理类会实现相同的接口或者是代理类继承被代理类。客户端通过代理类来访问&#xff08;调用&#xff09;被…

QT跨平台读写Excel

QT跨平台读写Excel 背景Excel工具CMakeLists.txt工程目录 背景 开发框架QT&#xff0c;makefile构建工具CMake&#xff0c;编译器MinGW Excel工具 考虑跨平台则不能使用针对微软COM组件的QAxObject来读写Excel&#xff0c;因此使用开源QtXlsx。 这里是将QXlsx当做源码嵌入使…

【Linux学习】Linux权限(二)

文章目录 &#x1f680;Linux权限管理&#x1f680;修改文件的所有者&#x1f680;修改文件或目录的所属组&#x1f680;同时修改为念的拥有者与所属组&#x1f680;文件类型&#x1f680;file指令&#x1f680;目录权限&#x1f680;umask指令&#x1f680;粘滞位 &#x1f68…

使用 Docker 部署 instantbox 轻量级 Linux 系统

1&#xff09;instantbox 介绍 GitHub&#xff1a;https://github.com/instantbox/instantbox instantbox 是一款非常实用的项目&#xff0c;它能够让你在几秒内启动一个主流的 Linux 系统&#xff0c;随起随用&#xff0c;支持 Ubuntu&#xff0c;CentOS&#xff0c; Arch Li…

c#+unity基础

序列化&#xff1a; [SerializeField]&#xff0c;点不出来&#xff0c;只能在面板上显示绑定游戏物体 //公有隐藏 特有函数 特有函数&#xff1a;不需要调用&#xff0c;自动执行 Awake最先执行->OnEable 面向对象思想 面向对象思想&#xff1a;分为具体对象和抽象对…

nas如何异地共享文件?

nas异地共享文件是一种通过网络实现不同地区电脑与电脑、设备与设备、电脑与设备之间的文件共享的技术。通过nas&#xff08;网络附加存储&#xff09;设备&#xff0c;用户可以在不同地点的电脑或设备之间快速、安全地共享文件和数据。本文将介绍nas异地共享文件的原理以及它在…

day4网络编程作业

#include <myhead.h> #define SER_IP "192.168.125.78" #define SER_PORT 69 #define CLI_IP "192.168.125.176" #define CLI_PORT 4399 //文件上传 void upload(int cfd,struct sockaddr_in sin)//服务器信息结构体传参 {//填充读写请求字符数组--&…

如何查看项目中使用的Qt版本

如何查看项目中使用的Qt版本 1.点击左下角电脑按钮查看Qt版本。 2.点击左侧栏项目按钮查看Qt版本。

代码编辑工具PilotEditPro18.4版本在Windows系统的下载与安装配置

目录 前言一、PilotEdit Pro安装二、使用配置总结 前言 “ PilotEdit Pro是一个功能强大且功能丰富的文本和代码编辑器&#xff0c;可满足程序员、开发人员和IT专业人员的不同需求。定位为一个多功能的编辑解决方案&#xff0c;PilotEdit Pro以其对广泛的文本和代码文件格式的…

【黑马头条】-day11热点文章实时计算-kafka-kafkaStream-Redis

文章目录 今日内容1 实时流式计算1.1 应用场景1.2 技术方案选型 2 Kafka Stream2.1 概述2.2 KafkaStream2.3 入门demo2.3.1 需求分析2.3.2 实现2.3.2.1 添加依赖2.3.2.2 创建快速启动&#xff0c;生成kafka流2.3.2.3 修改生产者2.3.2.4 修改消费者2.3.2.5 测试 2.4 SpringBoot集…

短视频批量采集软件|视频无水印下载提取工具

全新发布&#xff01;DY视频批量下载工具&#xff0c;实现轻松快捷的视频提取 为了更好地满足您的需求&#xff0c;我们自主研发了全新的DY视频批量下载工具。相较于市面上单个视频链接提取的工具&#xff0c;我们的产品更为便捷&#xff0c;不仅支持单个视频链接提取&#xf…

mysql 日环比 统计

接到一个任务&#xff0c;要计算日环比的情况。 16、查询销售额日环比情况 日环比&#xff1a; &#xff08;今日-昨日&#xff09;/ 昨日 的一个比率情况。 1&#xff0c;建表 DROP TABLE IF EXISTS sale; create table sale(id int not null AUTO_INCREMENT,record_date da…