前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jackson父子类多态处理(注解实现)

Jackson父子类多态处理(注解实现)

作者头像
用户7741497
发布2022-03-24 12:09:01
2.2K0
发布2022-03-24 12:09:01
举报
文章被收录于专栏:hml_知识记录
  1. 方案二的实现 1.1 @JsonTypeInfo注解 1.2 @JsonSubTypes注解 1.3 @JsonTypeName注解
  2. 案例
  3. 智能版:扩展@JsonTypeIdResolver的使用 3.1 自定义TypeIdResolver 3.2 实现方案
    • 3.2.1 定义解析器
    • 3.2.2 使用
    • 3.2.3 测试 推荐阅读

在Controller定义了入参后,随着业务的扩展,需要业务的扩展,需要不断的为入参新增字段,但是不同的业务需要使用不同的字段。若一直向入参中新增字段(不满足开闭原则)就会导致后期的不可维护性。

方案一:Controller层接收的是String类型,然后通过手动的方式来进行反序列化为子类对象。

方案二:使用Jackson的多态处理。

1. 方案二的实现

使用方式:

代码语言:javascript
复制
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include =  JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class, name = "1"),
        @JsonSubTypes.Type(value = TokenUser.class, name = "2"),
})
@Data
public class AbstractBaseEntity {
    private String contentType;
    private String userName;
    private String password;
}

jackson允许配置多态处理,当进行反序列化时,JSON数据匹配的对象可能有多个子类型,为了正确的读取对象的类型,我们需要添加一些类型信息。可以通过下面几个注解来实现:

1.1 @JsonTypeInfo注解

作用在接口/类上,被用来开启多态类型的处理,对基类/接口和子类/实现类都有效。

样例:@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true)

  • use:定义使用哪一种类型识别码,它有下面几个可选值:

枚举值

作用

JsonTypeInfo.Id.CLASS

使用完全限定类名做识别

JsonTypeInfo.Id.MINIMAL_CLASS

若基类和子类在同一包类,使用类名(忽略包名)作为识别码

JsonTypeInfo.Id.NAME

一个合乎逻辑的指定名称

JsonTypeInfo.Id.CUSTOM

自定义识别码,由@JsonTypeIdResolver对应

JsonTypeInfo.Id.NONE

不使用识别码

  • include(可选):指定识别码是如何被包含进去的,它有下面几个可选值:

枚举值

作用

JsonTypeInfo.As.PROPERTY

作为数据的兄弟属性

JsonTypeInfo.As.EXISTING_PROPERTY

作为POJO中已经存在的属性,需要手动set

JsonTypeInfo.As.EXTERNAL_PROPERTY

作为扩展属性

JsonTypeInfo.As.WRAPPER_OBJECT

作为一个包装的对象

JsonTypeInfo.As.WRAPPER_ARRAY

作为一个包装的数组

  • property(可选):制定识别码的属性名称:

此属性只有当:

  1. use为JsonTypeInfo.Id.CLASS(若不指定property则默认为@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property则默认为@c)、JsonTypeInfo.Id.NAME(若不指定property默认为@type),
  2. include为JsonTypeInfo.As.PROPERTY、JsonTypeInfo.As.EXISTING_PROPERTY、JsonTypeInfo.As.EXTERNAL_PROPERTY

时才有效。

  • defaultImpl(可选):如果类型识别码不存在或者无效,可以使用该属性来制定反序列化时使用的默认类型。
  • visible(可选,默认为false):是否可见 属性定义了类型标识符的值是否会通过JSON流成为反序列化器的一部分,默认为fale,也就是说,jackson会从JSON内容中处理和删除类型标识符再传递给JsonDeserializer。

1.2 @JsonSubTypes注解

作用于类/接口,用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,一般是配合@JsonTypeInfo在基类上使用,比如:

@JsonSubTypes的值是一个@JsonSubTypes.Type[]数组,里面枚举了多态类型(value对应子类)和类型的标识符值(name对应@JsonTypeInfo中的property标识名称的值,此为可选值。若不制定需由@JsonTypeName在子类上制定)

1.3 @JsonTypeName注解

作用于子类,用来为多态子类指定类型标识符的值

代码语言:javascript
复制
@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class),
        @JsonSubTypes.Type(value = TokenUser.class),
})
@Data
public class AbstractBaseEntity

+

代码语言:javascript
复制
@Data
@JsonTypeName("1")
public class RoleUser extends AbstractBaseEntity

等效于:

代码语言:javascript
复制
@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class, name = "1"),
        @JsonSubTypes.Type(value = TokenUser.class, name = "2"),
})
@Data
public class AbstractBaseEntity

2. 案例

基类的使用:

代码语言:javascript
复制
@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class, name = "1"),
        @JsonSubTypes.Type(value = TokenUser.class, name = "2"),
})
@Data
public class AbstractBaseEntity {
    /**
     * 1. 系列化时 @JsonTypeInfo 使用的是EXISTING_PROPERTY策略(已经存在的字段),故需要手动的填充这个字段。
     * 2. 在反序列化对象时,@JsonSubTypes.Type根据name属性的不同,来转化为不同的子类对象
     */
    private String contentType;
    private String userName;
    private String password;
}

子类的创建:

代码语言:javascript
复制
@Data
public class RoleUser extends AbstractBaseEntity{

    private String roleName;

    @Override
    public String toString() {
        return "RoleUser{" +
                "abstractBaseEntity='" + super.toString() + '\'' +
                "roleName='" + roleName + '\'' +
                '}';
    }
}
代码语言:javascript
复制
@Data
public class TokenUser extends AbstractBaseEntity {

    private String token;


    @Override
    public String toString() {
        return "TokenUser{" +
                "abstractBaseEntity='" + super.toString() + '\'' +
                "token='" + token + '\'' +
                '}';
    }
}

测试代码:

代码语言:javascript
复制
public class Test {

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        
        RoleUser roleUser = new RoleUser();
        roleUser.setRoleName("role-1");
        roleUser.setPassword("rolePwd");
        roleUser.setUserName("roleUserName");
        roleUser.setContentType("1");   

        TokenUser tokenUser = new TokenUser();
        tokenUser.setToken("token-1");
        tokenUser.setPassword("tokenPassword");
        tokenUser.setUserName("tokenUserName");
        tokenUser.setContentType("2");
        //序列化
        String roleStr = objectMapper.writeValueAsString(roleUser);
        String tokenStr = objectMapper.writeValueAsString(tokenUser);
        System.out.println(roleStr);
        System.out.println(tokenStr);
        //反序列化
        AbstractBaseEntity roleEntity = objectMapper.readValue(roleStr, AbstractBaseEntity.class);
        AbstractBaseEntity tokenEntity = objectMapper.readValue(tokenStr, AbstractBaseEntity.class);
        System.out.println(roleEntity);
        System.out.println(tokenEntity);
    }
}

Spring上的测试代码:

代码语言:javascript
复制
@Slf4j
@RestController
public class JacksonController {
    @RequestMapping("/test/js")
    @ResponseBody
    public String testEnvV3(@RequestBody AbstractBaseEntity entity) {
        RoleUser roleUser = null;
        if (entity.getContentType().equals("1")) {
            roleUser = (RoleUser) entity;
        }
        return JSON.toJSONString(roleUser);
    }
}

3. 智能版:扩展@JsonTypeIdResolver的使用

Jackson 多态序列化可以通过@JsonSubtypes来实现,但总觉得不是很方便,比如新增子类的时候都要去加一下JsonSubTypes。

有没有一个比较智能化的扩展?

有的@JsonTypeInfo使用JsonTypeInfo.Id.CUSTOM策略,然后自定义解析规则。

代码语言:javascript
复制
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(JacksonTypeIdResolver.class)

3.1 自定义TypeIdResolver

  • idFromValueAndType:是序列化的时候告诉序列化器怎么生成标识符。
  • typeFromId:是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来。

3.2 实现方案

3.2.1 定义解析器

代码语言:javascript
复制
public class JacksonTypeIdResolver implements TypeIdResolver {
    private JavaType baseType;

    @Override
    public void init(JavaType javaType) {
        baseType = javaType;
    }


    @Override
    public String idFromValue(Object o) {
        return idFromValueAndType(o, o.getClass());
    }


    /**
     * 序列化时填充什么对象
     */
    @Override
    public String idFromValueAndType(Object o, Class<?> aClass) {
        //有出现同名类时可以用这个来做区别
        JsonTypeName annotation = aClass.getAnnotation(JsonTypeName.class);
        if (annotation != null) {
            return annotation.value();
        }
        String name = aClass.getName();
        String[] splits = StringUtils.split(name, ".");
        String className = splits[splits.length - 1];
        return className;
    }


    /**
     * idFromValueAndType 是序列化的时候告诉序列化器怎么生成标识符
     * <p>
     * typeFromId是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来
     */
    @Override
    public JavaType typeFromId(DatabindContext databindContext, String type) {
        Class<?> clazz = getSubType(type);
        if (clazz == null) {
            throw new IllegalStateException("cannot find class '" + type + "'");
        }
        return databindContext.constructSpecializedType(baseType, clazz);
    }

    public Class<?> getSubType(String type) {
        Reflections reflections = ReflectionsCache.getReflections();
        Set<Class<?>> subTypes = reflections.getSubTypesOf((Class<Object>) baseType.getRawClass());
        for (Class<?> subType : subTypes) {
            JsonTypeName annotation = subType.getAnnotation(JsonTypeName.class);
            if (annotation != null && annotation.value().equals(type)) {
                return subType;
            } else if (subType.getSimpleName().equals(type) || subType.getName().equals(type)) {
                return subType;
            }
        }
        return null;
    }

    @Override
    public String idFromBaseType() {
        return idFromValueAndType(null, baseType.getClass());
    }

    @Override
    public String getDescForKnownTypeIds() {
        return null;
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;

    }
}

反射相关,引入依赖:

代码语言:javascript
复制
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>
代码语言:javascript
复制
public class ReflectionsCache {
    private static Reflections reflections;

    public static void setReflections(Reflections _reflections) {
        reflections = _reflections;
    }

    public static Reflections getReflections() {
        return reflections;
    }

}
代码语言:javascript
复制
@Service
public class ClassCacheInitializing implements InitializingBean {

    private String packages="com.tellme.jackson";

    /**
     * 反射会有点耗时,所以程序启动的时候加载完放到缓存里面去,后面要用的时候直接去缓存取
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        Reflections reflections = new Reflections(packages);
        if (reflections != null) {
            ReflectionsCache.setReflections(reflections);
        }
    }

3.2.2 使用

基类:

代码语言:javascript
复制
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(JacksonTypeIdResolver.class)
public class Animal {
    private String name;
}

子类(子类上无需在使用注解):

代码语言:javascript
复制
@Data
public class Dog extends Animal {
    private int age;

    @Override
    public String toString() {
        return "Dog{" +
                "animal=" + super.toString() +
                "age=" + age +
                '}';
    }
}

3.2.3 测试

代码语言:javascript
复制
public class TestEx {

    public static void main(String[] args) throws Exception {
        ClassCacheInitializing classCacheInitializing=new ClassCacheInitializing();
        classCacheInitializing.afterPropertiesSet();

        ObjectMapper objectMapper = new ObjectMapper();
        AnimalDto animalDto = new AnimalDto();
        Dog dog = new Dog();
        dog.setName("dog");
        dog.setAge(1);
        animalDto.setAnimal(dog);
        String s = objectMapper.writeValueAsString(animalDto);
        System.out.println(s);
        animalDto = objectMapper.readValue(s, AnimalDto.class);

        System.out.println(animalDto);
    }
}

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 方案二的实现
    • 1.1 @JsonTypeInfo注解
      • 1.2 @JsonSubTypes注解
        • 1.3 @JsonTypeName注解
        • 2. 案例
        • 3. 智能版:扩展@JsonTypeIdResolver的使用
          • 3.1 自定义TypeIdResolver
            • 3.2 实现方案
              • 3.2.1 定义解析器
              • 3.2.2 使用
              • 3.2.3 测试
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档