zoukankan      html  css  js  c++  java
  • Spring mvc 接口枚举类型数据格式化处理

    一.背景简述

      首先,我们都知道枚举实例有两个默认属性,name 和 ordinal,可通过 name()和ordinal()方法分别获得。其中 name 为枚举字面量(如 MALE,FEMALE),ordinal 为枚举实例默认次序(从0开始)

      《阿里巴巴Java开发手册》将接口中枚举的使用分为两类,即 接口参数和接口返回值,并规定: 接口参数可以使用枚举类型,但接口返回值不可以使用枚举类型(包括含枚举类型的POJO对象)。

    Java中出现的任何元素,在Gosling的角度都会有背后的思考和逻辑(尽管并非绝对完美,但Java的顶层抽象已经是天才级了),比如:接口、抽象类、注解、和本文提到的枚举。枚举有好处,类型安全,清晰直接,还可以使用等号来判断,也可以用在switch中。它的劣势也是明显的,就是不要扩展。可是为什么在返回值和参数进行了区分呢,如果不兼容,那么两个都有问题,怎么允许参数可以有枚举。当时的考虑,如果参数也不能用,那么枚举几乎无用武之地了。参数输出,毕竟是本地决定的,你本地有的,传送过去,向前兼容是不会有问题的。但如果是接口返回,就比较恶心了,因为解析回来的这个枚举值,可能本地还没有,这时就会抛出序列化异常。

    比如:你的本地枚举类,有一个天气Enum:SUNNY, RAINY, CLOUDY,如果根据天气计算心情的方法:guess(WeatcherEnum xx),传入这三个值都是可以的。返回值:Weather guess(参数),那么对方运算后,返回一个SNOWY,本地枚举里没有这个值,傻眼了

      当然,使用自定义字段 code 照样不能处理,对此,开发手册作者的回答如下

    主要是从防止这种序列化异常角度来考虑,使用code至少不会出大乱子。而catch序列化异常,有点像catch(NullPointerException e)一样代码过度,因为它是可预检异常。

      接口传值也不建议使用Ordinal,因为枚举示例应该是无序的,ordinal提供的顺序是不可靠的。所以我们应该使用自定义的枚举字段。

      我们定义了一个枚举类,继承了两个接口拥有两个字段,如下:

      

    public interface ValueEnum<T> {
        T value();
    }
    
    public interface DescriptionEnum {
        String description();
    }
    
    public enum Gender implements ValueEnum<Integer>,DescriptionEnum {
        /**
         * 性别男
         */
        MALE(1,"男"),
        /**
         * 性别女
         */
        FEMALE(2,"女");
    
        private Integer value;
    
        private String description;
    
        Gender(Integer value,String description) {
            this.value = value;
            this.description = description;
        }
    
        @Override
        public String description() {
            return description;
        }
    
        @Override
        public Integer value() {
            return value;
        }
        
    }

      有个使用Gender的pojo类User

    @Data
    public class User {
    
        private Long id;
    
        private String name;
    
        private Gender gender;
    
        private String email;
    }

    二. 使用枚举作为接口参数

      2.1 Spring 默认使用Bean接收枚举参数时支持 字面量,这也是我们常见的做法。

        如下:

    @Data
    public class UserCommand {
    
        private String name;
    
        private Gender gender;
    
        private String email;
    }
        @ApiOperation("添加用户")
        @PostMapping("/users")
        public User users(User command){
            User user = new User();
            BeanUtils.copyProperties(command,user);
            return user;
        }

      注意这种方式不支持枚举的ordinal值

      2.2 使用Json接收枚举参数

        Json数据都放在请求体中,后台使用注解@RequestBody+command bean接收(也可以从HttpServletRequest的getInputStream获取)

        

     @ApiOperation("添加用户")
        @PostMapping("/users")
        public User users(@RequestBody UserCommand userCommand) {
            User user = new User();
            BeanUtils.copyProperties(userCommand,user);
            return user;
        }

        这种方式支持字面量,ordinary  

      2.3 自定义@RequestBody 和@ResponseBody处理枚举参数

       2.3.1 单独使用@JsonValue

       

    public enum Gender implements ValueEnum<Integer>,DescriptionEnum{
        /**
         * 性别男
         */
        MALE(10,"男"),
        /**
         * 性别女
         */
        FEMALE(20,"女");
    
    
        
        private Integer value;
    
    
        private String description;
    
        Gender(Integer value,String description) {
            this.value = value;
            this.description = description;
        }
    
    
        @Override
        public String description() {
            return description;
        }
    
    
    
        @JsonValue
        @Override
        public Integer value() {
            return value;
        }
    
    
    }

      @JsonValue决定了序列化的字段,表明该枚举类型只能使用该字段值传值。它可标注在字段和getter方法上,推荐标注在getter方法上。因为标注在字段上,swagger参数列表只显示字面值,但实际不能使用字面值传值,这样会给使用该接口的开发人员造成误解。

      标注value字段上:

      

      标注在value方法上

      

      

      这种方案虽然简单,但是只能单独使用某个字段传值。

      2.3.2 使用@JsonValue+@JsonCreator,代码如下

     

    public enum Gender implements ValueEnum<Integer>,DescriptionEnum{
        /**
         * 性别男
         */
        MALE(10,"男"),
        /**
         * 性别女
         */
        FEMALE(20,"女");
    
    
        private Integer value;
    
    
        private String description;
    
        Gender(Integer value,String description) {
            this.value = value;
            this.description = description;
        }
    
        @JsonValue
        @Override
        public String description() {
            return description;
        }
    
    
    
        @Override
        public Integer value() {
            return value;
        }
    
        @JsonCreator
        public static Gender create(String value){
            try{
                return Gender.valueOf(value);
            }catch (IllegalArgumentException e){
                for (Gender gender : Gender.values()) {
                    try {
                        if (gender.value.equals(Integer.parseInt(value))) {
                            return gender;
                        }
                    }catch (NumberFormatException n) {
                        if (gender.description.equals(value)) {
                            return gender;
                        }
                    }
                }
                throw new IllegalArgumentException("No element matches "+value);
                }
    
        }
    
    }

      @JsonValue是可选的,标注在getter方法上或者字段上,但是标注字段上Swagger显示参数不起作用,它可决定枚举反序列化的字段。如下

      

      

      @JsonCreator 标注在静态方法上,表明使用该方法序列化和反序列化,方法内部是序列化的逻辑

      上面的示例代码可使用三种方式传值。枚举类型的字面值,value属性或description属性,。这种方案就比较灵活可以任意决定一个或多个字段传值

  • 相关阅读:
    Android碰到的怪问题
    Linux转换文件格式
    Android 打包签名 从生成keystore到完成签名
    重写SimpleCursorAdapter
    sqlite的数据导入 导出
    日语单词检索WebService 账户API和示例更新
    日语单词检索 WebService 试运行 和简单说明
    使用扩展方法,让菜单也可以Clone
    接口里面的静态方法痒啊
    创业难,守业更难
  • 原文地址:https://www.cnblogs.com/yumiaoxia/p/10414212.html
Copyright © 2011-2022 走看看