自定义 Jackson 注解与禁用某一特定的注解

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 类生成的对象
 1class Person {
 2
 3  public String name  = "Yanbin";
 4
 5  public String getPhone () {
 6    return "(312)666-8888";
 7  }
 8
 9  public int age = 100;
10
11  @JsonProperty("city")
12  public String location = "Chicago";
13
14}

想要对于某些用户生成

{"name":"Yanbin","age":"******","city":"Chicago","phone":"******"}

其他用户生成

{"name":"Yanbin","age":100,"city":"Chicago"}

即有条件的隐藏某些敏感信息。

希望自定义 @MaskField 来标 name 属性和 getPhone() 方法。

定义 @MaskField
1@Retention(RetentionPolicy.RUNTIME)
2@Target({ElementType.FIELD, ElementType.METHOD})
3@JacksonAnnotationsInside
4@JsonSerialize(using = MaskFieldSerializer.class)
5@interface MaskField {
6}

上面指示了标记为 @MaskField 字段或 getter 方法奖应用  MaskFieldSerializer 为序列化,总是输出为 ******

MaskFieldSerializer 类
1class MaskFieldSerializer extends JsonSerializer<Object> {
2
3  @Override
4  public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
5    throws IOException {
6    jgen.writeString("******");
7  }
8}

现在把 @MaskField 应用到  Person 类
 1class Person {
 2
 3  public String name  = "Yanbin";
 4
 5  @MaskField
 6  public String phone () {
 7    return "(312)666-8888";
 8  }
 9
10  @MaskField
11  public int age = 100;
12
13  @JsonProperty("city")
14  public String location = "Chicago";
15
16}

用代码测试一下
1ObjectMapper maskMapper = new ObjectMapper();
2System.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
 1class DisablingMaskFieldIntrospector extends JacksonAnnotationIntrospector {
 2
 3  @Override
 4  public boolean isAnnotationBundle(Annotation ann) {
 5    if (ann.annotationType().equals(MaskField.class)) {
 6      return false;
 7    } else {
 8      return super.isAnnotationBundle(ann);
 9    }
10  }
11
12}

如果是 MaskField 就 return false, 禁用  @MaskField 注解

该告诉你的 ObjectMapper 使用这个自定义 AnnotationIntrospector,下面的测试代码
1ObjectMapper showAllMapper = new ObjectMapper();
2showAllMapper.setAnnotationIntrospector(new DisablingMaskFieldIntrospector());
3System.out.println(showAllMapper.writeValueAsString(new Person()));

输出为

{"name":"Yanbin","age":100,"city":"Chicago"}

也就是要不要采用 DisablingMaskFieldIntrospector 就决定了我们禁止还是启用 @MaskField 注解的功能。


浪费点篇幅,上面完整的代码如下
 1import com.fasterxml.jackson.annotation.*;
 2import com.fasterxml.jackson.core.*;
 3import com.fasterxml.jackson.databind.*;
 4import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 5import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
 6
 7import java.io.IOException;
 8import java.lang.annotation.*;
 9
10public class TestJacksonAnnotation {
11
12  private static ObjectMapper maskMapper = new ObjectMapper();
13
14  private static ObjectMapper showAllMapper = new ObjectMapper() {{
15    setAnnotationIntrospector(new DisablingMaskFieldIntrospector());
16  }};
17
18  public static void main(String[] args) throws JsonProcessingException {
19    System.out.println(maskMapper.writeValueAsString(new Person()));
20    System.out.println(showAllMapper.writeValueAsString(new Person()));
21  }
22}
23
24class Person {
25
26  public String name  = "Yanbin";
27
28  @MaskField
29  public String phone () {
30    return "(312)666-8888";
31  }
32
33  @MaskField
34  public int age = 100;
35
36  @JsonProperty("city")
37  public String location = "Chicago";
38
39}
40
41class MaskFieldSerializer extends JsonSerializer<Object> {
42
43  @Override
44  public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
45    throws IOException {
46    jgen.writeString("******");
47  }
48}
49
50@Retention(RetentionPolicy.RUNTIME)
51@Target({ElementType.FIELD, ElementType.METHOD})
52@JacksonAnnotationsInside
53@JsonSerialize(using = MaskFieldSerializer.class)
54@interface MaskField {
55
56}
57
58class DisablingMaskFieldIntrospector extends JacksonAnnotationIntrospector {
59
60  @Override
61  public boolean isAnnotationBundle(Annotation ann) {
62    if (ann.annotationType().equals(MaskField.class)) {
63      return false;
64    } else {
65      return super.isAnnotationBundle(ann);
66    }
67  }
68
69}

执行后输出为

{"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 永久链接 https://yanbin.blog/customize-jackson-annotation-and-disable-specific-annotation/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。