有道无术,术尚可求,有术无道,止于术。
本系列Jackson 版本 2.17.0
源码地址:https://gitee.com/pearl-organization/study-jaskson-demo
文章目录
- 1. 需求场景
- 2. 混合注解
- 2.1 创建混合类
- 2.2 关联
- 2.3 测试
- 3. @JsonMixin
1. 需求场景
在某个疯狂星期四的下午,小坤👱正在啃着腿,偷偷刷着电脑版某音💃💃💃,突然经理的👴从旁边凑了过来,并道:这(人人)不错…
小坤👱:经经经…理
经理👴:没事,都是爷们儿,我懂…这里有个需求你做下
经理👴:认证项目中引入了一个第三包,登录时调用包中的认证方法返回令牌对象TokenInfo
,我们将令牌序列化后返回给前端
经理👴:令牌对象中的username
、password
属性添加了@JsonIgnore
注解,在序列化时会被忽略,birthday
属性格式为yyyy-MM-dd HH:mm:ss
::
public class TokenInfo {@JsonIgnoreString username;@JsonIgnoreString password;@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")Date birthday;// 省略......
}
经理👴:现在要求序列化时username
不能被忽略,birthday
的格式只需要具体到年月日
小坤👱:没问题,经理,交给我吧!
小坤👱看了看代码,稍作思考,这…还不简单吗…既然第三方包的对象无法修改,那我先定义一个VO
,使用注解指定序列化规则:
public class TokenInfoVO {String username;@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "GMT+8")Date birthday;// 省略......
}
然后将令牌对象转换为VO
再执行序列化:
ObjectMapper objectMapper = new ObjectMapper();// 模拟第三包返回令牌对象TokenInfo tokenInfo=new TokenInfo();tokenInfo.setUsername("王法");tokenInfo.setBirthday(new Date());tokenInfo.setPassword("123456");// 转换为对应格式的VOTokenInfoVO vo=new TokenInfoVO();vo.setUsername(tokenInfo.getUsername());vo.setBirthday(tokenInfo.getBirthday());// 序列化String value = objectMapper.writeValueAsString(vo);System.out.println(value);//
几分钟不到,小坤👱就已完成了需求,并满脸欢喜的在群里@
了经理:李总,刚才的需求已经做完了。
小坤👱内心OS
:怎么样,傻眼了吧,没想到吧,我这么快就做完了,厉害吧!!有我这里牛逼的开发,你就偷着乐吧你!!!
小坤👱紧紧盯着屏幕,期待这经理会在群里表扬几句…
可是没想到经理👴回话:小坤,你这是写的什么阿!!!有考虑过代码的可维护性、可扩展性、简洁性吗?明明一行代码就能搞定的问题,你这是写的屎山吗???
小坤👱刚浮起的笑意顿时僵住了:犊子,这是在玩我吧,不能修改第三方类,又要添加基于注解的序列化规则,还特么能有其他的处理方式?
2. 混合注解
Mixin
翻译为:混合类型;米心;混进;糅合;
Jackson
提供了一种混合注解机制(mix-in annotations
),允许开发者在不修改原始类的情况下,为其添加或覆盖特定的注解。
2.1 创建混合类
创建一个混合类,将需要重新处理的属性复制过来,并添加需要的注解,例如username
设置为不忽略,birthday
指定新的格式化模板:
public abstract class MixinTokenInfo {@JsonIgnore(value = false)String username;@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "GMT+8")Date birthday;
}
2.2 关联
将混合类与原始类关联起来,就可以动态地改变原始类的序列化或反序列化行为。
ObjectMapper
提供了关联方法:
/*** 向指定的类或接口添加混合注解,以扩充其功能** @param target 被覆盖的类(或接口)* @param mixinSource 混合类* @since 2.5*/public ObjectMapper addMixIn(Class<?> target, Class<?> mixinSource){_mixIns.addLocalDefinition(target, mixinSource);return this;}
示例代码:
// 关联objectMapper.addMixIn(TokenInfo.class,MixinTokenInfo.class);
2.3 测试
最终代码如下所示:
ObjectMapper objectMapper = new ObjectMapper();// 关联objectMapper.addMixIn(TokenInfo.class,MixinTokenInfo.class);// 模拟第三包返回令牌对象TokenInfo tokenInfo=new TokenInfo();tokenInfo.setUsername("王法");tokenInfo.setBirthday(new Date());tokenInfo.setPassword("123456");// 序列化String value = objectMapper.writeValueAsString(tokenInfo);System.out.println(value);
查看输出结果:
{"username":"王法","birthday":"2024-04-10"}
3. @JsonMixin
Spring Boot
默认使用Jackson
作为JSON
框架,例如自动配置中已经帮我们注册了一个ObjectMapper
:
@Bean@Primary // 主要的@ConditionalOnMissingBean // 在应用程序没有注册ObjectMapper时生效ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {return builder.createXmlMapper(false).build();}
在进行JSON
操作时,直接注入ObjectMapper
即可:
@AutowiredObjectMapper objectMapper;
也提供了相应的扩展,比如Spring Boot 2.7
版本提供了@JsonMixin
,启动时会扫描加载被@JsonMixin
标识的类,不需要再显式的调用addMixIn
方法声明关联关系,使用起来更加的方便。
@JsonMixin
注解源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonMixin {@AliasFor("type")Class<?>[] value() default {};@AliasFor("value")Class<?>[] type() default {};
}
在Spring Boot
工程中,混合类添加@JsonMixin
注解,并指定原始类:
@JsonMixin(TokenInfo.class) // 需要修改的目标类
public abstract class MixinTokenInfo {@JsonIgnore(value = false)String username;@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "GMT+8")Date birthday;
}
单元测试:
@AutowiredObjectMapper objectMapper;@Testvoid testMixIn() throws JsonProcessingException {// 模拟第三包返回令牌对象TokenInfo tokenInfo=new TokenInfo();tokenInfo.setUsername("王法");tokenInfo.setBirthday(new Date());tokenInfo.setPassword("123456");// 序列化String value = objectMapper.writeValueAsString(tokenInfo);System.out.println(value);}
查看输出结果:
{"username":"王法","birthday":"2024-04-11"}