jackson按照自定义注解序列化和反序列化属性

背景

在springboot-web项目中,绝大多时候都是使用json格式来传输数据,springboot默认使用jackson来转换,一般情况下,默认的转化设置已经可以满足要求。特殊一点的使用Jackson相关的注解辅助也能完成。

但是在一次开发过程中,要求对某些字段进行加解密,但是由于是spring微服务的项目,所以只在其中一个服务中需要加密,所以没有办法使用@JsonSerialize 和 @JsonDeserialize着两种jackson的注解。

解决思路

一开始想的是,jackson既然实现了自己的注解,那它为了满足用户的需求,肯定做了兼容让用户根据自己的需要做数据的转换。经过查询资料和源码,发现OjectMapper有一个注入内省注解处理器的方法:

java 复制代码
    public ObjectMapper setAnnotationIntrospector(AnnotationIntrospector ai) {
        _serializationConfig = _serializationConfig.with(ai);
        _deserializationConfig = _deserializationConfig.with(ai);
        return this;
    }

    public ObjectMapper setAnnotationIntrospectors(AnnotationIntrospector serializerAI,
            AnnotationIntrospector deserializerAI) {
        _serializationConfig = _serializationConfig.with(serializerAI);
        _deserializationConfig = _deserializationConfig.with(deserializerAI);
        return this;
    }

序列化

ObjectMapper在对象序列化时,会使用到 _serializationConfig这个配置。

java 复制代码
protected final void _configAndWriteValue(JsonGenerator g, Object value)
        throws IOException
    { 
        SerializationConfig cfg = getSerializationConfig();   //这个方法会返回 包含了 自定义注解解释类的的 _serializationConfig
        cfg.initialize(g); // since 2.5
        if (cfg.isEnabled(SerializationFeature.CLOSE_CLOSEABLE) && (value instanceof Closeable)) {
            _configAndWriteCloseable(g, value, cfg);
            return;
        }
        try {
            // _serializerProvider(cfg) 会构建一个默认的 DefaultSerializerProvider序列化对象
            _serializerProvider(cfg).serializeValue(g, value);
        } catch (Exception e) {
            ClassUtil.closeOnFailAndThrowAsIOE(g, e);
            return;
        }
        g.close();
    }
复制代码
DefaultSerializerProvider继承了SerializerProvider
java 复制代码
public JsonSerializer<Object> findTypedValueSerializer(Class<?> valueType,
            boolean cache, BeanProperty property)
        throws JsonMappingException
    {
        // Two-phase lookups; local non-shared cache, then shared:
        JsonSerializer<Object> ser = _knownSerializers.typedValueSerializer(valueType);
        if (ser != null) {
            return ser;
        }
        // If not, maybe shared map already has it?
        ser = _serializerCache.typedValueSerializer(valueType);
        if (ser != null) {
            return ser;
        }

        // Well, let's just compose from pieces:
        ser = findValueSerializer(valueType, property);  //这里获取对应的序列化实现
        TypeSerializer typeSer = _serializerFactory.createTypeSerializer(_config,
                _config.constructType(valueType));
        if (typeSer != null) {
            typeSer = typeSer.forProperty(property);
            ser = new TypeWrappedSerializer(typeSer, ser);
        }
        if (cache) {
            _serializerCache.addTypedSerializer(valueType, ser);
        }
        return ser;
    }

一直走到方法:

java 复制代码
 protected JsonSerializer<Object> _createAndCacheUntypedSerializer(Class<?> rawType)
        throws JsonMappingException
    {
        JavaType fullType = _config.constructType(rawType);
        JsonSerializer<Object> ser;
        try {
            ser = _createUntypedSerializer(fullType);  //创建序列化器
        } catch (IllegalArgumentException iae) {
            // We better only expose checked exceptions, since those
            // are what caller is expected to handle
            ser = null; // doesn't matter but compiler whines otherwise
            reportMappingProblem(iae, ClassUtil.exceptionMessage(iae));
        }

        if (ser != null) {
            // 21-Dec-2015, tatu: Best to cache for both raw and full-type key
            _serializerCache.addAndResolveNonTypedSerializer(rawType, fullType, ser, this);
        }
        return ser;
    }
java 复制代码
@Override
    @SuppressWarnings("unchecked")
    public JsonSerializer<Object> createSerializer(SerializerProvider prov,
            JavaType origType)
        throws JsonMappingException
    {
        // Very first thing, let's check if there is explicit serializer annotation:
        final SerializationConfig config = prov.getConfig();
        BeanDescription beanDesc = config.introspect(origType);
        //通过注解来创建序列化器
        JsonSerializer<?> ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo());
        if (ser != null) {
            return (JsonSerializer<Object>) ser;
        }
       ... 忽略其他
    }

最终:

java 复制代码
protected JsonSerializer<Object> findSerializerFromAnnotation(SerializerProvider prov,
            Annotated a)
        throws JsonMappingException
    {
        /**
            prov使用的是之前构建的 SerializationConfig 类,其中包含了 我们自定义的 AnnotationIntrospector
            getAnnotationIntrospector()可以获取到这个自定义的AnnotationIntrospector并调用其方法返回我们自定义的序列化器
        */
        Object serDef = prov.getAnnotationIntrospector().findSerializer(a);
        if (serDef == null) {
            return null;
        }
        JsonSerializer<Object> ser = prov.serializerInstance(a, serDef);
        // One more thing however: may need to also apply a converter:
        return (JsonSerializer<Object>) findConvertingSerializer(prov, a, ser);
    }

这样我们就能使用自定义注解+自定义的序列化器来实现字段的序列化。

反序列化

反序列化的过程和序列化的过程相似。

java 复制代码
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
        throws IOException
    {
        try (JsonParser p = p0) {
            Object result;
            JsonToken t = _initForReading(p, valueType);
            /**这里构建了 使用 _deserializationConfig的 DeserializationConfig 
                而_deserializationConfig中包含了我们自定义的注解解释器
            */
            final DeserializationConfig cfg = getDeserializationConfig();
            final DeserializationContext ctxt = createDeserializationContext(p, cfg);
            if (t == JsonToken.VALUE_NULL) {
                // Ask JsonDeserializer what 'null value' to use:
                result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
            } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
                result = null;
            } else {
                //这里开始反序列化数据
                JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
                if (cfg.useRootWrapping()) {
                    result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
                } else {
                    result = deser.deserialize(p, ctxt);
                }
                ctxt.checkUnresolvedObjectId();
            }
            if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
                _verifyNoTrailingTokens(p, ctxt, valueType);
            }
            return result;
        }
    }

最终也会执行到创建反序列化器的方法(DeserializationContext)中

java 复制代码
protected JsonDeserializer<Object> _createDeserializer(DeserializationContext ctxt,
            DeserializerFactory factory, JavaType type)
        throws JsonMappingException
    {
        final DeserializationConfig config = ctxt.getConfig();

        // First things first: do we need to use abstract type mapping?
        if (type.isAbstract() || type.isMapLikeType() || type.isCollectionLikeType()) {
            type = factory.mapAbstractType(config, type);
        }
        BeanDescription beanDesc = config.introspect(type);
        // Then: does type define explicit deserializer to use, with annotation(s)?
        /**
            这里通过获取注解序列化器
        */
        JsonDeserializer<Object> deser = findDeserializerFromAnnotation(ctxt,
                beanDesc.getClassInfo());
        if (deser != null) {
            return deser;
        }

        //... 忽略其他
    }

创建注解序列化器的方法:

java 复制代码
protected JsonDeserializer<Object> findDeserializerFromAnnotation(DeserializationContext ctxt,
            Annotated ann)
        throws JsonMappingException
    {
        /**
            ctxt中包含了我们自定的注解解释器 - _deserializationConfig。
        */
        Object deserDef = ctxt.getAnnotationIntrospector().findDeserializer(ann);
        if (deserDef == null) {
            return null;
        }
        JsonDeserializer<Object> deser = ctxt.deserializerInstance(ann, deserDef);
        // One more thing however: may need to also apply a converter:
        return findConvertingDeserializer(ctxt, ann, deser);
    }

这样就能得到我们自定义的注解解释器,并在注解解释器中返回我们自定义的字段序列化和反序列实现。

具体实现

首先我们需要自定义一个注解,以及一个包含了注解的实体类。

java 复制代码
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
java 复制代码
@Data
public class TestBean {
    private String name;

    private Integer age;

    @TestAnnotation
    private String serise;
}

然后实现自定义的注解解释器、序列化和反序列化实现:

自定义注解解释器实现 NopAnnotationIntrospector 类,并重写findSerializer()和findDeserializer(),在方法中判断是否包含注解,如果包含则返回我们自定义的序列化和饭序列化实现

java 复制代码
public class TestDataAnnotationIntrospector extends NopAnnotationIntrospector {
    @Override
    public Object findSerializer(Annotated am) {
        TestAnnotation annotation = am.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            return TestDataSerializer.class;
        }

        return null;
    }

    @Override
    public Object findDeserializer(Annotated am) {
        TestAnnotation annotation = am.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            return TestDataDeserializer.class;
        }

        return null;
    }
}

序列化和反序列化实现

java 复制代码
public class TestDataDeserializer extends StdDeserializer<String> {
    private static final long serialVersionUID = 1L;

    public TestDataDeserializer() {
        super(String.class);
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        // string
        String s = p.getValueAsString();
        return s + "------我加了结尾";
    }
}



public class TestDataSerializer extends StdSerializer<String> {
    private static final long serialVersionUID = 1L;

    public TestDataSerializer() {
        super(String.class);
    }

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeString("我加了开头---------" + value);
    }
}

测试代码:

java 复制代码
public static void main(String[] args){
        TestBean testBean = new TestBean();
        testBean.setName("这是名称");
        testBean.setAge(100);
        testBean.setSerise("这是被注解的数据啊啊啊啊");

        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.setAnnotationIntrospector(new TestDataAnnotationIntrospector());

        String json = null;
        try {
            json = objectMapper.writeValueAsString(testBean);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        System.out.println("json化结果:" + json);

        try {
            TestBean testBean1 = objectMapper.readValue(json, TestBean.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

输出结果:

序列化:

json化结果:{"name":"这是名称","age":100,"serise":"我加了开头---------这是被注解的数据啊啊啊啊"}

反序列化:

springMVC使用自定义的ObjectMapper

方式1:

通过覆盖:springMvc自带的MappingJackson2HttpMessageConverter类

java 复制代码
@Bean
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
        MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.setAnnotationIntrospector(new DataAnnotationIntrospector());
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        converter.setObjectMapper(objectMapper);
        return converter;
    }

方式2:

通过覆盖:springMvc自带的ObjectMapper类

java 复制代码
    @Bean
    ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.setAnnotationIntrospector(new DataAnnotationIntrospector());
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return objectMapper;
    }

方式3:

java 复制代码
@Configuration
public class ExtWebMvcConfig implements WebMvcConfigurer {
    @Bean
    ObjectMapper objectMapper(){
        Object[] objects = {
                SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
                , DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS
                , DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE
                , DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
                , DeserializationFeature.FAIL_ON_INVALID_SUBTYPE
                , SerializationFeature.FAIL_ON_EMPTY_BEANS
                , DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
        };

        Jackson2ObjectMapperFactoryBean objectMapper = new Jackson2ObjectMapperFactoryBean();
        objectMapper.setFeaturesToDisable(objects);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.setFindModulesViaServiceLoader(true);
        objectMapper.setAnnotationIntrospector(new DataAnnotationIntrospector());
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        
        objectMapper.afterPropertiesSet();
        return objectMapper.getObject();
    }

问题

1.@JsonIgnore失效

以上按照自定实现内省器的方式自定义注解序列化,在一般情况都没有问题,但是,如果是存在@JsonIgnore注解属性或方法,会导致@JsonIgnore失效甚至会报错。

原因:

NopAnnotationIntrospector中的hasIgnoreMarker 方法(是否有忽略Json的标记)和hasRequiredMarker(是否有必须序列化或反序列化的标记)方法没有做任何的实现,这是不符合实际的。

java 复制代码
/**
     * Method called to check whether given property is marked to
     * be ignored. This is used to determine whether to ignore
     * properties, on per-property basis, usually combining
     * annotations from multiple accessors (getters, setters, fields,
     * constructor parameters).
     */
    public boolean hasIgnoreMarker(AnnotatedMember m) { return false; }


    /**
     * Method that can be called to check whether this member has
     * an annotation that suggests whether value for matching property
     * is required or not.
     */
    public Boolean hasRequiredMarker(AnnotatedMember m) { return null; }

解决办法:

1.我们可以重写这hasIgnoreMarker方法和hasRequiredMarker方法。

2.我们自定义的内省器不要继承NopAnnotationIntrospector,改为继承 JacksonAnnotationIntrospector。

java 复制代码
public class TestDataAnnotationIntrospector extends JacksonAnnotationIntrospector{
    @Override
    public Object findSerializer(Annotated am) {
        TestAnnotation annotation = am.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            return TestDataSerializer.class;
        }

        return null;
    }

    @Override
    public Object findDeserializer(Annotated am) {
        TestAnnotation annotation = am.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            return TestDataDeserializer.class;
        }

        return null;
    }
}

2.@JsonProperty和@JsonIgnore同时存在导致@JsonIgnore失效

重写findNameForSerialization方法,先执行父类原来的逻辑,如果PropertyName为null或者空,则返回null,这样后面的逻辑就不会给PropertyName赋值了,最后也就不会取消Ignore了。

java 复制代码
public class TestDataAnnotationIntrospector extends JacksonAnnotationIntrospector {


    @Override
    public PropertyName findNameForSerialization(Annotated a)
    {
        PropertyName propertyName = super.findNameForSerialization(a);
        return propertyName == null || StringUtils.isEmpty(propertyName.getSimpleName()) ? null : propertyName;
    }

    @Override
    public Object findSerializer(Annotated am) {
        TestAnnotation annotation = am.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            return TestDataSerializer.class;
        }

        return null;
    }

    @Override
    public Object findDeserializer(Annotated am) {
        TestAnnotation annotation = am.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            return TestDataDeserializer.class;
        }

        return null;
    }
}
相关推荐
_oP_i1 小时前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx1 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康1 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘2 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意2 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上3 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进3 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人4 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.4 小时前
Mybatis-Plus
java·开发语言
不良人天码星4 小时前
lombok插件不生效
java·开发语言·intellij-idea