问题描述
项目使用SpringMVC框架,并用jackson库处理JSON和POJO的转换。在POJO转化成JSON时,有些属性我们不需要输出或者有些属性循环引用会造成无法输出。
- 例如:实体User其中包括用户名、密码、邮箱等,但是我们在输出用户信息不希望输出密码、邮箱信息;
- 例如:实体user和department是多对一的关系,user内保存着department的信息,那么json输出时会导致这两个实体数据的循环输出;
jackson默认可以使用JsonIgnoreProperties接口来定义要过滤的属性,然后使用ObjectMapper#addMixInAnnotations
来设置对应实体对应的JsonIgnoreProperties接口,这样就能达到过滤的目的。可是这样很不爽,因为如果你对n个实体对应有m种过滤需求就至少要建n*m个JsonIgnoreProperties接口。
解决方案
主要逻辑如下图
大致处理流程:
- 使用自定义注解controller方法
- 然后定义aop捕获所有controller方法
- 当aop捕获到controller方法时调用JavassistFilterPropertyHandler#filterProperties方法
- filterProperties读取注解并根据自定义注解使用javassist创建JsonIgnoreProperties临时实现类(同时缓存到map内,下次可直接取出)并存入当前线程内(ThreadJacksonMixInHolder, 使用threadlocal实现),
- 在springmvc输出json的类内自定义ObjectMapper, 从当前线程内取出JsonIgnoreProperties临时类, 调用ObjectMapper# addMixInAnnotations使之起效
- 最后使用ObjectMapper输出
用法:
定义aop, 用来捕获springmvc的controller方法
1 | package com.xiongyingqi.json.filter.aop; |
spring配置
1 | <!-- 启动mvc对aop的支持,使用aspectj代理 --> |
配置spring-mvc的messageconverter
1 | <bean |
重写spring的MappingJackson2HttpMessageConverter类,这样输出的json内容就能自定义
1 |
|
在方法上注解
Controller方法的示例,yxResourceSelfRelationsForSuperiorResourceId是YxResource内要过滤的属性:
1 |
|
主要类说明
自定义注解类:这些类是用于注解实体类输出json时要注解过滤的属性
IgnoreProperties.java
用于同时注解IgnoreProperty
和AllowProperty
1 |
|
IgnoreProperty.java
:过滤指定对象内的指定字段名
1 |
|
AllowProperty.java
:注解实体类允许的字段
1 |
|
核心处理类,用于处理自定义注解并将生成的类存入当前线程
1 | package com.xiongyingqi.jackson.impl; |
线程持有类,用于在当前线程内保存核心类处理过的自定义注解生成的MixIn注解,并且能提供ObjectMapper的生成
1 | package com.xiongyingqi.jackson.helper; |
测试
测试代码
1 | package com.xiongyingqi.jackson; |
测试结果
at com.xiongyingqi.jackson.JsonFilterPropertyTest.jsonTest(JsonFilterPropertyTest.java:80)
String =============== [{"name":"用户1","group":{"id":1,"name":"分组1"}},{"name":"用户1","group":{"id":1,"name":"分组1"}},{"name":"用户1","group":{"id":1,"name":"分组1"}},{"name":"用户4","group":{"id":2,"name":"分组2"}},{"name":"用户5","group":{"id":2,"name":"分组2"}},{"name":"用户6","group":{"id":2,"name":"分组2"}}]
性能与缺陷
- 主要是在map内存储了Javassist的临时类,每个注解(IgnoreProperties等)的方法的调用,对应在FilterPropertyHandler会处理一次注解并在内存内产生一个Javassist临时类,但是访问过一次之后该类就会读取map缓存
- ThreadJacksonMixInHolder:这个类的原理就是使用ThreadLocal在当前线程内存储处理过的annotation注解,java的容器或框架都是使用了该类,导致的效率问题应该不大
- 未知的bug
其他说明
其他框架内使用
如果不是spring-mvc框架也能使用这些代码来解决,只是必须要修改aop的捕获方法、使用new JavassistFilterPropertyHandler(false)禁用ResponseBody,以及在ObjectMapper输出使用自己定义的输出
源代码地址
代码已上传到maven中央库:
http://mvnrepository.com/artifact/com.xiongyingqi/common_helper
Maven Usage:
1 | <dependency> |