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;
    }
}
相关推荐
赶飞机偏偏下雨7 分钟前
【Java笔记】消息队列
java·开发语言·笔记
yacolex23 分钟前
Mac安装使用Gradle
spring·macos·gradle
豐儀麟阁贵27 分钟前
2.6 代码注释与编码规
java·开发语言
程序员三明治28 分钟前
【Mybatis从入门到入土】ResultMap映射、多表查询与缓存机制全解析
java·sql·缓存·mybatis·resultmap·缓存机制·多表查询
华仔啊33 分钟前
Java 重试机制没写对,线上很容易出问题!这份生产级方案请收好
java·后端
你不是我我36 分钟前
【Java 开发日记】什么是线程池?它的工作原理?
java·开发语言
Seven9737 分钟前
剑指offer-35、数组中的逆序对
java·leetcode
梵得儿SHI1 小时前
Java 反射机制深度解析:从运行时 “解剖” 类的底层逻辑
java·开发语言·反射·反射机制·private·类成员·反射的三大核心功能
豆沙沙包?1 小时前
2025年--Lc188--931. 下降路径最小和(多维动态规划,矩阵)--Java版
java·矩阵·动态规划
JAVA学习通1 小时前
Spring AI 1.0 GA 深度解析:Java生态的AI革命已来
java·人工智能·spring·springai