当前位置: 首页 > article >正文

Spring后端直接用枚举类接收参数,自定义通用枚举类反序列化器

在使用枚举类做参数时,一般会让前端传数字,后端将数字转为枚举类,当枚举类很多时,很可能不知道这个code该对应哪个枚举类。能不能后端直接使用枚举类接收参数呢,可以,但是受限。
Spring反序列默认使用的是Jacskon,反序列化枚举类时,可以根据枚举类的name或ordinal属性进行反序列化,这是因为Jacskon内置了EnumDeserializer,它可以根据name或ordinal属性进行反序列化。

public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        if (p.hasToken(JsonToken.VALUE_STRING)) {
            return _fromString(p, ctxt, p.getText());
        }

        // But let's consider int acceptable as well (if within ordinal range)
        if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
            if (_isFromIntValue) {
            	// 根据name
                return _fromString(p, ctxt, p.getText());
            }
   			// 根据ordinal
            return _fromInteger(p, ctxt, p.getIntValue());
        }
        if (p.isExpectedStartObjectToken()) {
            return _fromString(p, ctxt,
                    ctxt.extractScalarFromObject(p, this, _valueClass));
        }
        return _deserializeOther(p, ctxt);
    }

比如我们有一个接口:

    @PostMapping("/product")
    @ResponseBody
    public void product(@RequestBody Product product) {
        System.out.println(product.getStatus());
        System.out.println("ok");
    }

public class Product {
    private Status status;
    private String name;
    // getter and setter
}

public enum Status {
    ON_LINE(1000, "在线"),
    OFF_LINE(2000, "下线")
    ;
    private int code;
    private String desc;
    Status(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

PostMan请求
在这里插入图片描述

这两种写法都可以。
status使用数字方式,只能是0或1,即枚举类的ordinal值.但是这种方式和我们的使用习惯不同,我们一般会自定义code,而不是使用ordinal。如果向根据自定义code反序列化枚举类,该如何实现呢?

参照Spring的方式,大概思路应该是自定义一个反序列化器,然后再需要使用自定义反序列化器的对象上加上@JsonDeserialize以覆盖Spring默认使用的EnumDeserializer。

自定义枚举类反序列化器

public class StatusDeser extends JsonDeserializer<Status> {
    @Override
    public Status deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        final int code = p.getIntValue();
        return Status.getByCode(code);
    }
}

给Status增加一个getByCode,且指定反序列化器。

@JsonDeserialize(using = StatusDeser.class)
public enum Status {
    ON_LINE(1000, "在线"),
    OFF_LINE(2000, "下线")
    ;
    private int code;
    private String desc;
    Status(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public static Status getByCode(int code) {
        final Status[] values = Status.values();
        for (int i = 0; i < values.length; i++) {
            if (values[i].code == code) {
                return values[i];
            }
        }
        throw new RuntimeException("不合法的code值");
    }
}

此时就可以这样传值了
在这里插入图片描述
注意,如果直接使用枚举类做接收参数,接口和body应该这样写

    @PostMapping("/status")
    @ResponseBody
    public void status(@RequestBody Status status) {
        System.out.println(status);
        System.out.println("ok");
    }

在这里插入图片描述

统一处理枚举类

上面自定义的反序列化器可以处理某一种枚举,如果枚举很多,每写一个枚举类都要写一个与之对应的反序列化器,有点麻烦,而且一旦忘记写或不知道要写就麻烦了。如何对枚举类做统一处理呢?可以让枚举类都继承一个接口,我们的反序列化器对接口类型处理。

定义统一接口

public interface BaseEnum {
    Integer getCode();
}

统一接口中有一个返回code值的方法,前端传这个code值,我们根据这个code值反序列化出对应的枚举对象。

定义统一反序列化器

因为我们的接口下可以有多种实现类枚举,那我们在反序列化的时候要反序列化成哪种枚举类呢?怎么在运行时知道我们的目标枚举类呢?这个时候要使用StdDeserializer和ContextualDeserializer。
StdDeserializer中有目标对象类型,ContextualDeserializer可以在反序列化时获取目标对象类型信息。两者结合就可以在运行时获取到目标对象类型信息,动态创建反序列化器。
在这里插入图片描述

public class BaseEnumDeserial extends StdDeserializer<BaseEnum> 
								implements ContextualDeserializer {

    protected BaseEnumDeserial(Class<?> vc) {
        super(vc);
    }

    // Jackson通过反射创建BaseEnumDeserial对象,这个对象不会用于正真的反序列化,因为它没有真实的类信息
    // 真正用于反序列化的对象会通过createContextual重新创建
    public BaseEnumDeserial() {
        this(BaseEnum.class);
    }

    @Override
    public BaseEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {

        // 反序列目标对象继承自BaseEnum且是枚举类型
        if (BaseEnum.class.isAssignableFrom(_valueClass) && Enum.class.isAssignableFrom(_valueClass)) {
            final int code = p.getIntValue();
            BaseEnum[] enumConstants = (BaseEnum[]) _valueClass.getEnumConstants();
            for (int i = 0; i < enumConstants.length; i++) {
                if (code == enumConstants[i].getCode()) {
                    return enumConstants[i];
                }
            }
        }
        return null;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        // 从上下文获取目标对象类型
        final Class<?> rawClass = ctxt.getContextualType().getRawClass();
        // new出来的反序列化器不用我们缓存,一种类型的反序列化器的createContextual方法只会执行一次,执行后的结果Jackson自己会缓存
        return new BaseEnumDeserial(rawClass);
    }
}

注意,有的地方文章使用property获取目标对象类型,只有在枚举类作为其他对象的属性被反序列化时,property才有值,如果时直接反序列化枚举对象,则property是null,所以还是直接从上下文中取类型比较好。

// property可能为null
final Class<?> rawClass = property.getType().getRawClass();

枚举类改造

// 反序列器加在接口上,就不用在每个枚举类上加了
@JsonDeserialize(using = BaseEnumDeserial.class)
public interface BaseEnum {
    Integer getCode();
}
// 实现BaseEnum 接口
public enum Status implements BaseEnum {
    ON_LINE(1000, "在线"),
    OFF_LINE(2000, "下线")
    ;
    private int code;
    private String desc;
    Status(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
	// 删除了getByCode方法

    @Override
    public Integer getCode() {
        return code;
    }
}

定义接口

    @PostMapping("/product")
    @ResponseBody
    // 枚举类在其他对象中
    public void product(@RequestBody Product product) {
        System.out.println(product.getStatus());
        System.out.println("ok");
    }

    @PostMapping("/status")
    @ResponseBody
    // 直接反序列化枚举类
    public void status(@RequestBody Status status) {
        System.out.println(status);
        System.out.println("ok");
    }

在这里插入图片描述
此时,就可以前端传数字,后端直接用枚举类型接收,不用再做数字到枚举类型的转化了。改造之后,下面这种方式就不行了,只能使用code反序列化,而不能使用name反序列化了。
在这里插入图片描述


http://www.kler.cn/news/314744.html

相关文章:

  • IT行业:未来发展的无限可能
  • 【医学半监督】置信度指导遮蔽学习的半监督医学图像分割
  • 51单片机-系列-数码管中断和定时器
  • Lsposed Java HOOK原理及检测
  • 我的AI工具箱Tauri版-VideoIntroductionClipCut视频介绍混剪
  • Nacos与Eureka的区别:深入解析微服务中的服务注册与发现
  • npm切换为淘宝镜像源
  • GPU加速生物信息分析的尝试
  • 数据结构之存储位置
  • AIGC专栏15——CogVideoX-Fun详解 支持图文生视频 拓展CogVideoX到256~1024任意分辨率生成
  • Web_php_include 攻防世界
  • C++20 std::format
  • Windows下如何定时执行自定义任务
  • 2024年中国研究生数学建模竞赛C题——解题思路
  • 开源PHP导航网源码/精美简约网址导航收录网站/QQ技术导航程序
  • MySQL 索引事务
  • PyCharm的使用
  • 英特尔AI加速器Gaudi 3下周发布,挑战NVIDIA统治地位!
  • 连续数组问题
  • 『功能项目』QFrameWork框架重构OnGUI【63】
  • 深度学习02-pytorch-04-张量的运算函数
  • Selenium4.0实现自动搜索功能
  • 链式前向星建图
  • 【MySQL】 索引
  • Facebook隐私设置指南:如何更好地保护个人信息
  • 【二十二】【QT开发应用】QScrollArea控件应用1,C++11 R原始字符串字面量
  • Oracle(139)如何创建和管理数据库用户?
  • 1.3 计算机网络的分类
  • Hadoop的一些高频面试题 --- hdfs、mapreduce以及yarn的面试题
  • tensorflow同步机制