内容简介:需求如下,在一个类中,有一些字段属性,其是否输出并不是由字段上的JsonIgnore来决定,而是根据从上下文(如request)中传递过来的某些参数决定。如下类:当上下文值为 field1 时,则表示 A 的最终输出json 中没有字段 field1. 当上下文为 field2 时, 则最终输出没有字段 field2. 其它情况则输出所有字段。本文讨论Jackson中的处理方式,如果使用 Fastjson,则有很多方式处理,这里不表.
需求如下,在一个类中,有一些字段属性,其是否输出并不是由字段上的JsonIgnore来决定,而是根据从上下文(如request)中传递过来的某些参数决定。如下类:
class A { @ContextIgnored("field1") private String field1; @ContextIgnored("field2") private String field2; }
当上下文值为 field1 时,则表示 A 的最终输出json 中没有字段 field1. 当上下文为 field2 时, 则最终输出没有字段 field2. 其它情况则输出所有字段。
本文讨论Jackson中的处理方式,如果使用 Fastjson,则有很多方式处理,这里不表.
本文描述通过利用 JsonIgnore 注解处理方式解决此问题 和 通过 PropertyFilter 两种方式来完成此需求的处理方式。
利用 AnnotationIntrospector 中 hasIgnoreMarker 实现属性过滤
在标准的类处理中,jackson在处理每个类时,均会为此类生成1个 JsonSerializer 对象,在此场景中应该为 BeanSerializer 对象,而类中属性的处理,则是由 BeanPropertyWriter 来完成。即可以理解为在处理对象时,会先有1个解析类和属性的过程存在,如果在此过程中,检测到某个属性不应该最终输出,则不会产生相应属性的 BeanPropertyWriter 对象,即达到过滤输出的目的。
在正常的场景中,我们可以通过 注解 JsonIgnore, 将其加到属性上,即解析时即会过滤到属性。而实际实现,则是由类 JacksonAnnotationIntrospector 中 的 hasIgnoreMarker 来完成,则就是通过读取注解来判断属性是否应该被exclude掉。ObjectMapper中默认的 AnnotationIntrospector 即是 JacksonAnnotationIntrospector 来完成,但我们可以通过 方法 ObjectMapper.setAnnotationIntrospector 来重新指定自定义的实现。如下参考代码.
class VerifyIntrospetor extends JacksonAnnotationIntrospector { public boolean hasIgnoreMarker(AnnotatedMember m) { boolean f = super.hasIgnoreMarker(m); if(!f) { ContextIgnored anno = _findAnnotation(m, ContextIgnoreed.class); if(anno.value() == "验证值") return true; } return f; } }
如上代码,即在原来只处理注解 JsonIgnore 的基础之上,再加上处理 注解 ContextIgnored.
但是在Jackson中,此上的代码只能被调用1次,即针对单个类,ObjectMapper会缓存每个class所对应的 JsonSerializer 实例。当上下文改变时,ObjectMapper 并不会重新再创建新的 JsonSerializer,而是直接使用已经缓存过的 对象来处理,这样实际上就达不到最初的需求。相应的缓存类,可参考类 DefaultSerializerProvider.findTypedValueSerializer 方法实现 ,以及其中的类 SerializerCache. 由于当前jackson的实现, SerializerCache 为1个final类,并不支持扩展,因此修改 DefaultSerializerProvider 中属性的方式不可处理。
不过可以通过类似Composite的方式来解决此问题,通过方法 ObjectMapper.setSerializerProvider 可以重新使用1个新的 DefaultSerializerProvider 对象,此此类是可以被继承的。当 ObjectMapper 在writeValue时,均会调用 DefaultSerializerProvider 中 serializeValue 方法来进行序列化操作。更准确的说,会先调用 createInstance 方法产生1个新的 DefaultSerializerProvider 来完成对象序列化操作。那么在此过程中,可以通过1个类似 Map 来组合不同上下文的处理。即在 createInstance 时,根据当前上下文的值委派给具体的类来进行处理。而当委派之后,再由后面的 DefaultSerializerProvider.Impl 类来完成最终 JsonSerializer 的创建和处理过程。我们构建的 Composite 对象即完成了 上下文 中转换的中间隔离作用,即根据 上下文值 的不同会有多个 DefaultSerializerProvider 存在,但具体对象的内部缓存的 JsonSerializer 即会根据上下文的不同有自己的处理逻辑。
示意代码如下:
class Composite { private Map<上下文, DefaultSerializerProvider> map; public Composite createInstance(SerializationConfig config, SerializerFactory jsf) { return map.computeIfAbsent(上下文, k-> new DefaultSerializerProvider.Impl()); } }
在具体实现中, 还需要考虑 DefaultSerializerProvider.copy 方法,这个在具体使用时具体实现即可.
利用 PropertyFilter 拦截属性输出
相比第1种手法的复杂及晦涩(可以理解为直接怼),这种手法就比较简单了。自 jackson 2.3 之后,提供接口 PropertyFilter ,以及对应的注解 JsonFilter 来允许对特定的类在处理字段时,进行额外的过滤处理操作。所谓的过滤操作,即将属性的实际操作转交由接口来完成。其接口下的1个参考实现 SimpleBeanPropertyFilter 即提供了 包含 和 排除的实现代码。我们只需要实现方法 boolean include(BeanPropertyWriter writer) 即可完成判断是否输出某个属性的操作。
此 PropertyFilter 的使用即在类上,通过 JsonFilter 定义此类的过滤器, 然后通过 ObjectMapper.setFilterProvider(SimpleFilterProvider) 添加 具体实现即可, 两都通过 value 来完成匹配过程。
PropertyFilter 接口是在每一次属性序列化均会处理,即意味着,如果方法 include(BeanPropertyWriter writer) 的返回值是变化的,即完成了动态拦截属性的作用。参考实现如下:
public class ContextIgnoredFilter extends SimpleBeanPropertyFilter { @Override protected boolean include(BeanPropertyWriter writer) { ContextIgnored anno = writer.getAnnotation(ContextIgnored.class); //上下文值与注解值判断处理... return true; } }
这种实现方法,不需要添加额外的 AnnotationIntrospector, 也不需要调整 ObjectMapper 的 DefaultSerializerProvider 属性,并且在代码Review上更简单易懂。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。