消息转换器 HttpMessageConverter

消息转化器的作用

将请求报文转化为Java对象

将Java对象转化为响应报文

消息转换器接口

public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();

	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
} T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException; void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException; }

转换器加载流程

消息转换器是在项目启动的时候通过WebMvcConfigurationSupport进行加载,当getMessageConverters()被调用的时候会通过configureMessageConverters()、addDefaultHttpMessageConverters()和extendMessageConverters()三个方法进行初始话消息转换器。生成的消息转换器放在List<HttpMessageConverter<?>> messageConverters集合中

	protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList();
// 加载委托给WebMvcConfigurer类型的Bean
this.configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
// 加载默认的转换器
this.addDefaultHttpMessageConverters(this.messageConverters);
}
// 加载扩展消息转换器
this.extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}

自定义消息转换器参考下面

(1)直接注入Bean 的方式替换

(2)实现 WebMvcConfigurer#extendMessageConverters 接口方法

fastjson与jackjson

问题

在springboot中使用fastjson的@jsonField无效

原因:在springboot默认有json(jackjson)解析工具,所以使用fastjson不会生效

解决方案替换默认的解析工具(笔者不推荐,这里根据自己项目决定)

fastjson替换默认的jackjson

第一种方法bean方法

package com.anson.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter; import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List; @Configuration
public class WebConfig { /**
* @Author anson
* @Description 配置消息转换器
* @Date: 2019-12-8 11:23:33
* @version: 1.0
* new HttpMessageConverters(true, converters);
* 一定要设为true才能替换否则不会替换
* @return 返回一个消息转换的bean
*/
@Bean
public HttpMessageConverters fastJsonMessageConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
//需要定义一个convert转换消息的对象;
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
//添加fastJson的配置信息;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
//全局时间配置
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
//处理中文乱码问题
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//在convert中添加配置信息.
fastConverter.setSupportedMediaTypes(fastMediaTypes);
fastConverter.setFastJsonConfig(fastJsonConfig); converters.add(0, fastConverter);
return new HttpMessageConverters(converters);
}
}

第二种方法实现webMvcConfigurer

@Configuration
public class WebConfigure implements WebMvcConfigurer{ /**
* 配置消息转换器
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//需要定义一个convert转换消息的对象;
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
//添加fastJson的配置信息;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
//全局时间配置
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
//处理中文乱码问题
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//在convert中添加配置信息.
fastConverter.setSupportedMediaTypes(fastMediaTypes);
fastConverter.setFastJsonConfig(fastJsonConfig);
converters.add(0,fastConverter);
}
}

jackjson注解实现原理

参考:https://blog.csdn.net/qq_18515155/article/details/128891441?spm=1001.2014.3001.5501

这里以Jackson中常用的@JsonFormat注解为例,目标就是将Company对象序列化。

public class Company {
/**
* 开张时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date openDate; public Date getOpenDate() {
return openDate;
} public void setOpenDate(Date openDate) {
this.openDate = openDate;
}
} public class Test {
public static void main(String[] args) {
Company company = new Company();
company.setOpenDate(new Date()); ObjectMapper objectMapper = new ObjectMapper(); String s;
try {
s = objectMapper.writeValueAsString(company);
System.out.println(s);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
} 执行结果如下:
{"openDate":"2023-02-05"}

处理过程简述:

寻找合适的序列化器

序列化器二次处理(主要用于传递属性元信息)

执行序列化

详细如下:

一·寻找合适的序列化器



(1)根据当前数据类型从缓存中获取对应的序列化器

(2)如果获取不到,则创建序列化器

(3)根据Company上的注解获取序列化器(Company上没有注解)

(4)根据当前的注解内省器获取能够处理 步骤3 拿到的注解的序列化器(默认的注解内省器是JacksonAnnotationIntrospector,对于注解@JsonSerialize则返回注解指定的序列化器,对于注解JsonRawValue则返回RawSerializer)

(5)如果获取不到,再根据是否实现了JsonSerializable接口或者标注了 @JsonValue注解获取对应的序列化器(当前Company都没有)

(6)如果没有,就只能创建通用的对象处理器BeanSerialzer

(7)在创建BeanSerialzer之前,需要先去找到对象的属性元信息

(8)为对象的属性(Compnay.openDate)找到合适的序列化器(类似上面的步骤,一步一步找,这里最终没有找对于openDate属性的序列化器)

(9)把属性元信息设置到对象元信息里,然后new一个BeanSerialzer并返回

(10)拿到返回的序列化器以后,如果该序列化器实现了ContextualSerializer接口,则调用它的createContextual方法进行二次处理,并返回序列化器

二.序列化器二次处理(主要用于传递属性元信息)

步骤10即为序列化器二次处理:当找到合适的序列化器以后,还需要根据是否这个序列化器实现了ContextualSerializer接口,该接口声明了方法JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)

该接口可以拿到属性的元信息,如属性上的注解信息,根据元信息可以做一些二次处理,然后再返回序列化器,再用这个返回的序列化器做序列化处理。

三.执行序列化



(1)序列化对象

(2)序列化对象的字段

(3)动态获取能处理字段的序列化器

(4)检索内置的序列化器集合

(5)根据类型从内置的序列化器集合获取对应的序列化器,即根据openDate的类型Date.class获取到DateSerializer

(6)序列化器二次处理

(7)设置需要传递到序列化器中的上下文信息

(8)将openDate上的JsonFormat注解的pattern字段值传递到DateSerializer,用来在序列化openDate时知道应该转为哪种时间格式

(9)调用DateSerializer.serialze进行字段序列化

jackjson实现数据脱敏

脱敏即是对数据的部分信息用脱敏符号(*)处理。

自定义注解:

package com.chinatower.platform.awh.framework;

import com.chinatower.platform.awh.framework.enums.SentitiveFieldEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @Description :
* @Date : 2023/5/23
* @Author :
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveField {
SentitiveFieldEnum type();
}

枚举类:

package com.chinatower.platform.awh.framework.enums;

/**
* @Description :
* @Date : 2023/5/23
* @Author :
*/
public enum SentitiveFieldEnum {
/**
* 身份证号
*/
ID_CARD,
/**
* 姓名
*/
NAME,
/**
* 手机号
*/
PHONE,
}

工具类:

package com.chinatower.platform.awh.framework.config;

import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils; /**
* @Description :
* @Date : 2023/5/23
* @Author :
*/
public class SensitiveDataUtils {
public static String encryptCard(String card){
String left = StringUtils.left(card, 1);
return StringUtils.rightPad(left,card.length(),"*");
}
public static String encryptPhone(String card){
String left = StringUtils.left(card, 2);
return StringUtils.rightPad(left,card.length(),"*");
}
public static String encryptName(String card){
String left = StringUtils.left(card, 3);
return StringUtils.rightPad(left,card.length(),"*");
}
}

自定义处理注解的序列化器

package com.chinatower.platform.awh.framework.config;

import com.chinatower.platform.awh.framework.SensitiveField;
import com.chinatower.platform.awh.framework.enums.SentitiveFieldEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer; import java.io.IOException; /**
* @Description :
* @Date : 2023/5/23
* @Author :
*/
public class SensitiveFieldSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SentitiveFieldEnum eum ;
public SensitiveFieldSerializer(){}
public SensitiveFieldSerializer(SentitiveFieldEnum enu){this.eum = enu;} @Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { SentitiveFieldEnum sentitiveFieldEnum = eum;
if (sentitiveFieldEnum == null){
return;
}
switch (sentitiveFieldEnum){
case ID_CARD:
s=SensitiveDataUtils.encryptCard(s);
break;
case NAME:
s=SensitiveDataUtils.encryptName(s);
break;
case PHONE:
s=SensitiveDataUtils.encryptPhone(s);
break;
default:break; }
jsonGenerator.writeObject(s);
} @Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
//获取注解
System.out.println(1);
if (beanProperty != null){
SensitiveField annotation = beanProperty.getAnnotation(SensitiveField.class);
SentitiveFieldEnum type = annotation.type();
return new SensitiveFieldSerializer(type);
}
//返回处理枚举
System.out.println(2);
return serializerProvider.findNullValueSerializer(null);
}
}

自定义注解内省器

package com.chinatower.platform.awh.framework;

import com.chinatower.platform.awh.framework.config.SensitiveFieldSerializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; /**
* @Description :
* @Date : 2023/5/23
* @Author :
*/
public class SensitiveFieldAnnotationIntrospector extends NopAnnotationIntrospector {
@Override
public Object findSerializer(Annotated am) {
SensitiveField annotation = am.getAnnotation(SensitiveField.class);
if (annotation !=null){
return SensitiveFieldSerializer.class;
}
return super.findSerializer(am);
}
}

Jackson序列化配置

package com.chinatower.platform.awh.framework.config;

import com.chinatower.platform.awh.framework.SensitiveFieldAnnotationIntrospector;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; /**
* @Description :
* @Date : 2023/5/23
* @Author :
*/
@Configuration
public class JacksonConfig { @Bean
public SensitiveFieldAnnotationIntrospector sensitiveFieldAnnotationIntrospector(){
return new SensitiveFieldAnnotationIntrospector();
} @Bean
public SensitiveFieldSerializer sensitiveFieldSerializer(){
return new SensitiveFieldSerializer();
} @Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector){
ObjectMapper mapper = builder.createXmlMapper(false).build();
AnnotationIntrospector annotationIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
AnnotationIntrospector pair = AnnotationIntrospector.pair(annotationIntrospector, introspector);
mapper.setAnnotationIntrospector(pair);
return mapper;
}
}

springboot的bean是单例模式

测试

/ 定义一个对象,字段标有注解
@Data
public class User {
@SensitiveField(type = SensitiveFieldTypeEnum.NAME)
private String name;
@SensitiveField(type = SensitiveFieldTypeEnum.PHONE)
private String phone;
private Integer age;
}
@GetMapping("/get")
public User get() {
User user = new User();
user.setAge(20);
user.setName("徐小莫");
user.setPhone("18211843612");
return user;
}
执行结果:
{
"name": "***",
"phone": "182****3672",
"age": 20
}

代码分析:

(1)如何找到处理自定义注解的序列化器

就要通过注解內省器找到合适的序列化器。

所以我们自定义了一个注解內省器,并把它加入到了当前Jackson注解內省器集合里:

        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
// 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
objectMapper.setAnnotationIntrospector(pair);

(2)**如何不影响已有的Jackson序列化配置 **

实际的SpringBoot项目中,对于Controller的返回数据,经常会在配置文件或者配置类进行一些Jackson的自定义配置。

如果针对当前的自定义注解序列化器新建一个ObjectMapper:

        ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
SensitiveFieldSerializer sensitiveFieldSerializer = new SensitiveFieldSerializer();
simpleModule.addSerializer(String.class, sensitiveFieldSerializer);
objectMapper.registerModule(simpleModule);
AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
AnnotationIntrospector newIntro = AnnotationIntrospectorPair.pair(annotationIntrospector,new SensitiveFieldAnnotationIntrospector());
objectMapper.setAnnotationIntrospector(newIntro);

那这个ObjectMapper不会包含原来的那些配置,因为他是一个新的对象。

要保证原来的配置生效,就需要对SpringBoot自动配置的ObjectMapper动手脚。

通过分析JacksonAutoConfiguration.class这个自动配置类

        @Bean
JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
return new JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
} @Bean
@Scope("prototype")
//这个注解是多例模式
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
this.customize(builder, customizers);
return builder;
}
@Bean
@Primary
@ConditionalOnMissingBean
// 这个注解代表没有自定义的ObjectMqpper bean对象时才会在这里创建
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}

发现SpringBoot会将配置信息封装成Jackson2ObjectMapperBuilder,进而去创建ObjectMapper对象。

所以需要通过注入已有的Jackson2ObjectMapperBuilder对象来创建我们自己的ObjectMapper,再将自定义注解内省器加入到它的內省器集合,使我们的自定义注解序列化器生效:

    @Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
// 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
objectMapper.setAnnotationIntrospector(pair);
return objectMapper;
}

自定义注解实现数据序列化时进行数据脱敏(基于springboot默认jackjson)、消息转换器HttpMessageConverter的更多相关文章

  1. ASP.Net中关于WebAPI与Ajax进行跨域数据交互时Cookies数据的传递

    本文主要介绍了ASP.Net WebAPI与Ajax进行跨域数据交互时Cookies数据传递的相关知识.具有很好的参考价值.下面跟着小编一起来看下吧 前言 最近公司项目进行架构调整,由原来的三层架构改 ...

  2. 自定义JsonResult处理JSON序列化DateTime类型数据(Ext4.2+ASP.NET MVC 4)

    最近项目中前台页面使用Extjs4.2 ,在后台ASP.NET MVC4返回的DateTime类型的数据错返回的DateTime类型的JsonResult的结果中的值是“\/Date(13784461 ...

  3. ASP.Net WebAPI与Ajax进行跨域数据交互时Cookies数据的传递

    前言 最近公司项目进行架构调整,由原来的三层架构改进升级到微服务架构(准确的说是服务化,还没完全做到微的程度,颗粒度没那么细),遵循RESTFull规范,使前后端完全分离,实现大前端思想.由于是初次尝 ...

  4. SpringMVC源码剖析5:消息转换器HttpMessageConverter与@ResponseBody注解

    转自 SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Spring源码 ...

  5. 用自定义注解实现fastjson序列化的扩展

    这篇文章起源于项目中一个特殊的需求.由于目前的开发方式是前后端分离的,基本上是通过接口提供各个服务. 而前两天前端fe在开发中遇到了一些问题:他们在处理字符串类型的时间时会出现精度丢失的情况,所以希望 ...

  6. [大牛翻译系列]Hadoop(16)MapReduce 性能调优:优化数据序列化

    6.4.6 优化数据序列化 如何存储和传输数据对性能有很大的影响.在这部分将介绍数据序列化的最佳实践,从Hadoop中榨出最大的性能. 压缩压缩是Hadoop优化的重要部分.通过压缩可以减少作业输出数 ...

  7. 使用Spring Aop自定义注解实现自动记录日志

    百度加自己琢磨,以下亲测有效,所以写下来记录,也方便自己回顾浏览加深印象之类,有什么问题可以评论一起解决,不完整之处也请大佬指正,一起进步哈哈(1)首先配置文件: <!-- 声明自动为sprin ...

  8. Java如何自定义注解

    本文主要是记录所学,以供后续参考.注解是Java 1.5引入的,Java自定义注解是通过运行时靠反射获取注解,注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定 ...

  9. SpringMVC日志管理(自定义异常及自定义注解)

    近期为了规范公司老旧项目的日志,主要也是为了便于日后错误排查以及加强对业务系统的监控,准备对原有的日志输出进行简单的改造. 解决思路 1.通过自定义异常来将可能出现的问题分为两大类,业务类及系统类.同 ...

  10. jmeter 导入csv数据中json格式数据取值不完整

    1.jmeter中添加csv数据文件时,数据是json格式 2.jmeter中执行取值发现只取了一部分 分析原因,json格式数据,中间有逗号,而csv是根据逗号来分割的,这回导致我们取值错位. 解决 ...

随机推荐

  1. 玩转服务器之网站篇:新手使用WordPress搭建博客和静态网站部署

    静态网站部署和WordPress搭建博客都是网站运营中常见的工作.静态网站是一种不需要服务器端脚本的网站形式,通常使用HTML.CSS和JavaScript等静态资源进行构建和显示.而WordPres ...

  2. 包含引用类型字段的自定义结构体,能作为map的key吗

    1. 引言 在 Go 语言中,map是一种内置的数据类型,它提供了一种高效的方式来存储和检索数据.map是一种无序的键值对集合,其中每个键与一个值相关联.使用 map 数据结构可以快速地根据键找到对应 ...

  3. 【HarmonyOS】HarmonyOS应用APP与HAP包签名信息查看方法

    ​HarmonyOS可以通过DevEco Studio 构建对应的APP包或者是HAP包,对于签名我们有两种方式: DevEco Studio提供了根据开发者信息生成自动调试签名的能力方便各位开发者进 ...

  4. 如何构建您的第一部AWS数据库服务

    目录 2.1. 基本概念解释 2.2. 技术原理介绍 2.3. 相关技术比较 实现步骤与流程 2.3.1 准备工作:环境配置与依赖安装 2.3.2 核心模块实现 2.3.3 集成与测试 4. 应用示例 ...

  5. 阿里云 MongoDB 创建库添加用户并授权

    先通过 root 进到 admin 库, 右击test 选择用户管理 测试连接

  6. python学习--采集弹幕信息

    # -*- coding: utf-8 -*-"""Created on Mon Nov 4 12:00:12 2019 @author: DELL"" ...

  7. Doris写入数据异常提示actual column number in csv file is less than schema column number

    版本信息: Flink 1.17.1 Doris 1.2.3 Flink Doris Connector 1.4.0 写入方式 采用 String 数据流,依照社区网站的样例代码,在sink之前将数据 ...

  8. 普通用户启动 supervisor 报 HTTP 错误(strace)

    公司的开发对生产环境都有普通用户 www 的权限,采用堡垒机登录到生产环境的机器. 默认 supervisor 使用 root 用户启动,开发没有权限直接修改配置和操作 supervisor 管理的进 ...

  9. C#获取文件MD5

    什么是MD5? ​ MD5 Message-Digest Algorithm,MD5信息摘要算法.一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于 ...

  10. Html转换PDF(Java实用版)

    前言: 在工作当中,遇到了需要把HTML页面转化为PDF文档,有很多中实现,如下进行一个对比,大家个借鉴去进行使用 各实现对比表 于Windows平台进行测试: 此博客仅基于IText和基于WKHtm ...