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屬性,。這種方案就比較靈活可以任意決定一個或多個字段傳值


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM