jackson mixin 特性简单应用

jackson mixin 特性简单应用

前言

前段时间遇到一个问题

场景

  1. 项目中有一个手机号脱敏的需求
  2. 项目中涉及脱敏的 VO 类也挺多. 粗略看了一下有几十个,用法就更多了

当时选择的脱敏方案是使用 jackson 的序列化注解方案,形如

1
@JsonSerialize(using = DesensitizedPhoneSerializer.class)

其中的 DesensitizedPhoneSerializer 是一个自定义的序列化器, 需要继承 com.fasterxml.jackson.databind.JsonSerializer 抽象类, 主要需要重写它的一个方法

1
2
3
4
5
6
7
8
9
10
/**
* Core method for serializing a value of the type this serializer handles.
*
* @param value The value to serialize; cannot be {@code null}.
* @param gen The generator used to output the resulting JSON content.
* @param serializers The provider that can be used to get serializers for
* any objects that the value contains.
* @throws IOException If an I/O error occurs during the generation process.
*/
public abstract void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException;

问题

项目中微服务之间调用使用的是 OpenFeign, 这个框架基于 http 请求并进行动态代理封装的

依旧会使用 jackson 来进行序列化与反序列化, 网络传输的响应体也就会被 DesensitizedPhoneSerializer 脱敏手机号

但微服务之间的调用有些时候需要获取到手机号明文

处理

方案

方案一

需要进行 rpc 内部调用的接口应该和对普通用户的接口职责分离, 这样可以做到接口级别的隔离

在对内的接口中传输明文, 在对外的接口中响应脱敏的密文

方案二

如果有一个方案能在进行序列化时得知接口是否为对内, 也可以在 DesensitizedPhoneSerializer 内部决定是否进行脱敏处理

比较

方案一涉及的范围就比较大了, 而且现有的系统对旧接口的依赖已经是一团乱麻了, 分离也不是一件容易的事情

程序界有伟人曾经说过, “能跑就行”

关于方案二, 可以利用 jackson 的 Mixin 特性来实现, 这样改动也是会比较小的

总结

使用方案二, 改动成本和风险都是较小的

实践

Mixin 特性 demo

原本需要响应的 UserVO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVO implements Serializable {

private static final long serialVersionUID = 7799431123917020619L;

@JsonSerialize(using = ToStringSerializer.class)
private Long id;

@JsonSerialize(using = DesensitizedPhoneSerializer.class)
private String phone;

private String desc;

}

添加一个用来替换 DesensitizedPhoneSerializer 序列化器的 UserVOMixin

1
2
3
4
5
6
7
8
9
10
11
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class UserVOMixin implements Serializable {

private static final long serialVersionUID = -793190335567089250L;

@JsonSerialize
public String phone;

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final var objectMapper1 = new ObjectMapper();
UserVO userVO = UserVO.builder()
.id(100L)
.phone("18603216630")
.desc("My name's Zhangsan")
.build();

System.out.println("=============================脱敏==============================");
System.out.println(objectMapper1.writeValueAsString(userVO));

final var objectMapper2 = new ObjectMapper();
// 替换掉原有的 UserVO
objectMapper2.addMixIn(UserVO.class, UserVOMixin.class);
System.out.println("============================未脱敏==============================");
System.out.println(objectMapper2.writeValueAsString(userVO));

输出

1
2
3
4
=============================脱敏==============================
{"id":"100","phone":"186****6630","desc":"My name's Zhangsan"}
============================未脱敏==============================
{"id":"100","phone":"18603216630","desc":"My name's Zhangsan"}

应用

有了这个特性的帮助, 为 OpenFeign 创建一个单独的 ObjectMapper 对象用于序列化, 并添加需要进行 mixin 的类就大功告成了

或许你会觉得, 不实用 jackson, 换成 fastjson2 或者 gson 作为 OpenFeign 的序列化方案, 这样也可以忽略 jackson 的 @JsonSerialize 注解

这样也可以, 但奥卡姆剃刀有句名言说道”如无必要, 勿增实体”

作者

wuhunyu

发布于

2025-11-17

更新于

2025-11-17

许可协议