在Springboot + Mybaitis-plus 项目中利用Jackson实现json对java多态的(反)序列化器

Jackson允许配置多态类型处理,当JSON面对的转换对象是一个接口、抽象类或者一个基类的时候,可以通过一定配置实现JSON的转换。在实际项目中,Controller层接收入参以及在Dao层将对象以json的形式存入数据库时都可能会遇到这个问题。而Springboot和mp都支持使用Jackson处理json,从而可以利用Jackson的特点,解决这一问题。

注意

为了代码简洁,这里的代码忽略了set和get方法和构造函数

在本例中,父类Zoo有两个子类Dog和Cat类

public static class Zoo {
  
    private String name;
    private AnimalTypeEnum animalType;
  
}

父类Zoo中,包含一个代表动物种类的枚举字段

public enum AnimalTypeEnum {
    DOG("dog"),
    CAT("cat");
    private final String name;
}

对于子类Dog包含一个速度属性

public static class Dog extends Zoo {
    private Double speed;
}

对于子类Cat包含一个尺寸属性

public static class Cat extends Zoo {
    private Integer size;
}

我们想做的事情是根据Zoo中的动物类型枚举字段animalType,将JSON反序列化为两种子类

方法一

使用Jackson提供的处理注解可以实现上述功能

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "animalType",
        visible = true
)
@JsonSubTypes(
        {
                @JsonSubTypes.Type(value = Dog.class, name = "DOG"),
                @JsonSubTypes.Type(value = Cat.class, name = "CAT")
        }
)
public static class Zoo {
  
    private String name;
    private AnimalTypeEnum animalType;
  
}

@JsonTypeInfo()

该注解表示对该类开启多态类型处理,包含四个属性

use 代表使用哪一种类型识别码

JsonTypeInfo.Id.NAME 是本例中选择的类型识别码,意指一个指定的名字

include代表识别码是如何包含进JSON

JsonTypeInfo.As.EXISTING_PROPERTY 代表POJO中存在的类型
property 指定类型表示码的属性名称

“animalType” 就是POJO中的类型字段名
visible 代表类型标识符是否会进入反序列化,默认false

由于这里我们同样需要该字段反序列化,所以设置为true

@JsonSubTypes()

该注解用于给出给定类的子类

@JsonSubTypes.Type[]数组中给出了多态类和property中指定属性某个值之间的绑定关系。在上例中,Dog类和animalType = DOG的值进行了绑定

在父类Zoo上加入如上注解之后,即可实现多态反序列化

对应测试

public void method1Test() throws JsonProcessingException {

    ObjectMapper objectMapper = new ObjectMapper();
    Cat cat = new Cat("小猫", AnimalTypeEnum.CAT, 20);
    Dog dog = new Dog("小狗", AnimalTypeEnum.DOG, 30.03);

    String catJson = objectMapper.writeValueAsString(cat);
    String dogJson = objectMapper.writeValueAsString(dog);

    log.debug(catJson);
    log.debug(dogJson);

    //反序列化
    Zoo catZoo = objectMapper.readValue(catJson, Zoo.class);
    Zoo dogZoo = objectMapper.readValue(dogJson, Zoo.class);

    //类型一致
    assertEquals(catZoo.getClass(),cat.getClass());
    assertEquals(dogZoo.getClass(),dog.getClass());

    //参数值一致
    assertEquals(20,((Cat)catZoo).getSize());
    assertEquals(30.03,((Dog)dogZoo).getSpeed());

}

image

可以看到,经过添加注解可以实现我们的需求

这样不管是springboot还是mybaitis-plus进行反序列化的时候,都通过注解的信息按照我们的要求进行反序列化

方法二

在项目中,一个基类会有很多的子类,并且随着项目的深入,子类可能会越来越多。使用上面的方法,需要不停的添加@JsonSubTypes中的内容,十分繁琐。这种写法是 **违反开闭原则(OCP)**的。

通过阅读源码,我们可以看到,其多态处理的基本原理就是将子类何其对应的名称之间的绑定关系注册到ObjectMapper中。

方法二的思路是给每个子类增加一个注解@JsonTypeName(value = “”),然后通过扫描所有带有注解的类,将所有带有标记的类注册到ObjectMapper中。

在Springboot中自定义ObjectMapper有很多办法,可以参考在SpringBoot中自定义 Jackson的ObjectMapper

首先生成一个ObjectMapper 的bean

@Configuration
public class ObjectMapperConfig {
    @Bean
    @Primary
    //使这个bean优先被注入
    public ObjectMapper objectMapper() {

        ObjectMapper objectMapper = new ObjectMapper();
      
        //使用reflection框架,获取本包下的所有带有@JsonTypeName的注解
        Reflections reflections = new Reflections("cn.");
        Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(JsonTypeName.class);
        //将这个上面扫描得到的类注册进这个ObjectMapper中
        objectMapper.registerSubtypes(classSet);
      
        //这里是将我们定义好的objectMapper set 进 Mybaitis-plus的Jackson处理器中,从而使得MP也可以				顺利的进行反序列化
        JacksonTypeHandler.setObjectMapper(objectMapper);
        return objectMapper;
    }
}

父类只需要添加这样一个注解

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "animalType",
        visible = true
)
public static class Zoo {
  
    private String name;
    private AnimalTypeEnum animalType;
  
}

子类添加注解

@JsonTypeName("DOG")
public static class Dog extends Zoo {
    private Double speed;
}

方法三

在我们的场景中,分类标识符是一个枚举类型。因此,我们希望将所有的子类和标识符名称对应的信息全部放在该枚举类中,使得仅通过枚举类就可以绑定好所有子类和名称之间的关系。

定义一个接口和注解,并在接口上使用了这个注解

@JsonSubTypeEnum.JsonSubTypeAnnotation
public interface JsonSubTypeEnum {
 
    Class<?> getJsonType();

    @Documented
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface JsonSubTypeAnnotation {
    }
}

这个接口定义了一个获取子类类信息的方法

public enum AnimalType implements JsonSubTypeEnum {
    DOG(Dog.class),
    CAT(Cat.class),
    ;
    private final Class<? extends Animal> animalClass;

    @Override
    public Class<?> getJsonType() {
        return this.animalClass;
    }
}

让需要用于分类的枚举实现这个接口,枚举中的animalClass属性,用来记录该标识符对应的子类的类别。

再来看ObjectMapper bean

@Bean
@Primary
public static ObjectMapper getObjectMapper(){
    ObjectMapper objectMapper = new ObjectMapper();
    Reflections reflections = new Reflections("com.");
  //获取所有带有自定义注解的类
    Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(JsonSubTypeEnum.JsonSubTypeAnnotation.class)//将其中的枚举类拿出来处理
      for (Class<?> enumTyp : classSet) {
        if (!enumTyp.isEnum()) {
            continue;
        }
        final Object[] enumConstants = enumTyp.getEnumConstants();
        for (Object e : enumConstants) {
            if (e instanceof JsonSubTypeEnum) {
                //将每个子类和标识符绑定注册进入objectMapper
                final Class<?> subType = ((JsonSubTypeEnum) e).getJsonType();
                final String name = ((Enum<?>) e).name();
                objectMapper.registerSubtypes(new NamedType(subType, name));
            }
        }
    }
         //这里是将我们定义好的objectMapper set 进 Mybaitis-plus的Jackson处理器中,从而使得MP也可以				顺利的进行反序列化
   JacksonTypeHandler.setObjectMapper(objectMapper);
   return objectMapper;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java,使用关键字"polymorphism"来实现多态。"polymorphism"是指一个对象具有多种形态,可以实现多态的机制有两种:继承与接口实现多态的关键是要理解父类与子类之间的关系,子类继承了父类的属性和方法,同时又可以根据需要重新定义自己的属性和方法,从而实现不同的行为。在Java,使用关键字"extends"来实现继承,使用关键字"implements"来实现接口,这两种机制都可以实现多态。例如: ``` public class Shape { public void draw() { System.out.println("Drawing a shape"); } } public class Circle extends Shape { @Override public void draw() { System.out.println("Drawing a circle"); } } public interface Drawable { void draw(); } public class Square implements Drawable { @Override public void draw() { System.out.println("Drawing a square"); } } ``` 在这个例子,Shape类是一个父类,Circle类继承了它,并重写了父类的draw()方法,Square类实现了Drawable接口,并实现接口的draw()方法。这样,我们就可以通过Shape、Circle和Square三个类来实现多态。例如: ``` Shape s1 = new Circle(); // 多态 s1.draw(); // 调用Circle类的draw()方法 Drawable d1 = new Square(); // 多态 d1.draw(); // 调用Square类的draw()方法 ``` 在这里,我们通过父类Shape和接口Drawable来定义了一个通用的类型,然后分别用子类Circle和Square来实现这个类型,从而实现多态

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值