自学内容网 自学内容网

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反序列化了。
在这里插入图片描述


原文地址:https://blog.csdn.net/weixin_41535316/article/details/142426433

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!