Jackson 是 Playfrmework 2 中默认的 JSON 处理框架,先前是 GSON,JSON 是 Playframework 中的第一等公民,可见 Jackson 在 Playframewok 中的重要地位。Jackson 提供了一系列的注解可用,像 @JsonIgnore, @JsonProperty, @JsonUnwrapped, @JsonFilter 等。人的需求总是很难得到满足,所以免不了还是要定义自己的注解。比如有这样一个需求,JavaBean 中被 @MaskField(这个即将成为我第一个自定义的注解) 标记的属性或 getter 方法,总是输出为 ******
, 无此标记的属性或方法输出原始值。
我尝试过 @JsonFilter 或是单纯的自定义 JsonSerializer, 并不怎么如意。本人最终的实现方式涉及到
- @JacksonAnnotationsInside -- 用来创建自己的 @MaskField 注解
- JsonSerializer -- 被 @MaskField 标记的字段采用自定义的 JsonSerializer 来序列化
- JacksonAnnotationIntrospector -- 禁用某一特定的注解,这样可以在做任意时候启用或禁用 @MaskField
再次重复需求,对于下面的 Person 类生成的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Person { public String name = "Yanbin"; public String getPhone () { return "(312)666-8888"; } public int age = 100; @JsonProperty("city") public String location = "Chicago"; } |
想要对于某些用户生成
{"name":"Yanbin","age":"******","city":"Chicago","phone":"******"}
其他用户生成
{"name":"Yanbin","age":100,"city":"Chicago"}
即有条件的隐藏某些敏感信息。
希望自定义 @MaskField 来标 name 属性和 getPhone() 方法。
定义 @MaskField
1 2 3 4 5 6 |
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) @JacksonAnnotationsInside @JsonSerialize(using = MaskFieldSerializer.class) @interface MaskField { } |
上面指示了标记为 @MaskField 字段或 getter 方法奖应用 MaskFieldSerializer 为序列化,总是输出为 ******
MaskFieldSerializer 类
1 2 3 4 5 6 7 8 |
class MaskFieldSerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString("******"); } } |
现在把 @MaskField 应用到 Person 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Person { public String name = "Yanbin"; @MaskField public String phone () { return "(312)666-8888"; } @MaskField public int age = 100; @JsonProperty("city") public String location = "Chicago"; } |
用代码测试一下
1 2 |
ObjectMapper maskMapper = new ObjectMapper(); System.out.println(maskMapper.writeValueAsString(new Person())); |
输出结果没错了,就是
{"name":"Yanbin","age":"******","city":"Chicago","phone":"******"}
凡是标记了 @MaskField 的字段或 getter 方法最终输出统统为 ******, 还记得还有个需求是对于某些时候还希望看到原貌,即
{"name":"Yanbin","age":100,"city":"Chicago"}
也就是有时要只禁用 @MaskField 注解。Jackson 可以调用
maskMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
来禁用所有的注解,那么像 @JsonProperty, @JsonIgnore 这样的注解如何是好,肯定不能一棍子打死,所以这里只是需要适时的把 @MaskField 禁用即可。这时候要用到 JacksonAnnotationIntrospector 来选择某个 Annotation 来禁用了。
因而自定义 DisablingMaskFieldIntrospector
1 2 3 4 5 6 7 8 9 10 11 12 |
class DisablingMaskFieldIntrospector extends JacksonAnnotationIntrospector { @Override public boolean isAnnotationBundle(Annotation ann) { if (ann.annotationType().equals(MaskField.class)) { return false; } else { return super.isAnnotationBundle(ann); } } } |
如果是 MaskField 就 return false, 禁用 @MaskField 注解
该告诉你的 ObjectMapper 使用这个自定义 AnnotationIntrospector,下面的测试代码
1 2 3 |
ObjectMapper showAllMapper = new ObjectMapper(); showAllMapper.setAnnotationIntrospector(new DisablingMaskFieldIntrospector()); System.out.println(showAllMapper.writeValueAsString(new Person())); |
输出为
{"name":"Yanbin","age":100,"city":"Chicago"}
也就是要不要采用 DisablingMaskFieldIntrospector 就决定了我们禁止还是启用 @MaskField 注解的功能。
浪费点篇幅,上面完整的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import java.io.IOException; import java.lang.annotation.*; public class TestJacksonAnnotation { private static ObjectMapper maskMapper = new ObjectMapper(); private static ObjectMapper showAllMapper = new ObjectMapper() {{ setAnnotationIntrospector(new DisablingMaskFieldIntrospector()); }}; public static void main(String[] args) throws JsonProcessingException { System.out.println(maskMapper.writeValueAsString(new Person())); System.out.println(showAllMapper.writeValueAsString(new Person())); } } class Person { public String name = "Yanbin"; @MaskField public String phone () { return "(312)666-8888"; } @MaskField public int age = 100; @JsonProperty("city") public String location = "Chicago"; } class MaskFieldSerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString("******"); } } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) @JacksonAnnotationsInside @JsonSerialize(using = MaskFieldSerializer.class) @interface MaskField { } class DisablingMaskFieldIntrospector extends JacksonAnnotationIntrospector { @Override public boolean isAnnotationBundle(Annotation ann) { if (ann.annotationType().equals(MaskField.class)) { return false; } else { return super.isAnnotationBundle(ann); } } } |
执行后输出为
{"name":"Yanbin","age":"******","city":"Chicago","phone":"******"}
{"name":"Yanbin","age":100,"city":"Chicago"}
实际的操作就是根据不同的条件使用 maskMapper 或是 showAllMapper 来进行序列化。
参考:1. https://github.com/swagger-api/swagger-core/issues/982
2. http://stackoverflow.com/questions/12921812/create-a-custom-jackson-annotation
[…] 非常感谢 http://yanbin.blog/customize-jackson-annotation-and-disable-specific-annotation/ […]
[…] http://yanbin.blog/customize-jackson-annotation-and-disable-specific-annotation/ 虽然我们并没有使用这个文章的做法 […]
[…] http://yanbin.blog/customize-jackson-annotation-and-disable-specific-annotation/ 虽然我们并没有使用这个文章的做法 […]