使用背景
在项目中需要将数据库中的gender(1男,0女),读取时做一个转换,转换为文字 男、女。同时,有另一个字段 info_tpye(三个不同的类型,数据库存储的是 1,2,3,但是需要展示对应的文字信息)。于是,使用bean对象集成枚举,在读取、插入数据时进行转换。
《阿里巴巴Java开发手册》将接口中枚举的使用分为两类,即 接口参数和接口返回值,并规定: 接口参数可以使用枚举类型,但接口返回值不可以使用枚举类型(包括含枚举类型的POJO对象)。
小小讨论:
Java中出现的任何元素,在Gosling的角度都会有背后的思考和逻辑(尽管并非绝对完美,但Java的顶层抽象已经是天才级了),比如:接口、抽象类、注解、和本文提到的枚举。枚举有好处,类型安全,清晰直接,还可以使用等号来判断,也可以用在switch中。它的劣势也是明显的,就是不要扩展。可是为什么在返回值和参数进行了区分呢,如果不兼容,那么两个都有问题,怎么允许参数可以有枚举。当时的考虑,如果参数也不能用,那么枚举几乎无用武之地了。参数输出,毕竟是本地决定的,你本地有的,传送过去,向前兼容是不会有问题的。但如果是接口返回,就比较恶心了,因为解析回来的这个枚举值,可能本地还没有,这时就会抛出序列化异常。
比如:你的本地枚举类,有一个天气Enum:SUNNY, RAINY, CLOUDY,如果根据天气计算心情的方法:guess(WeatcherEnum xx),传入这三个值都是可以的。返回值:Weather guess(参数),那么对方运算后,返回一个SNOWY,本地枚举里没有这个值,傻眼了
定义接口 和 枚举类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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为lombok注解)
1 2 3 4 5 6 7
| @Data public class User { private Long id; private String name; private Gender gender; private String email; }
|
使用枚举作为接口参数
Spring 默认使用Bean接收枚举参数时支持 字面量,这也是我们常见的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13
| @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值
使用Json接收枚举参数
Json数据都放在请求体中,后台使用注解 @RequestBody+command bean接收(也可以从HttpServletRequest的getInputStream获取)
1 2 3 4 5 6 7
| @ApiOperation("添加用户") @PostMapping("/users") public User users(@RequestBody UserCommand userCommand) { User user = new User(); BeanUtils.copyProperties(userCommand,user); return user; }
|
这种方式支持字面量,ordinary
自定义@RequestBody 和@ResponseBody处理枚举参数
单独使用@JsonValue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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方法上
这种方案虽然简单,但是只能单独使用某个字段传值。
使用@JsonValue+@JsonCreator,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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属性,。这种方案就比较灵活可以任意决定一个或多个字段传值
示例
配置文件
1 2 3
| # application.properties中 mybatis-plus.type-handlers-package=com.qingtengcloud.utils.enumHandler mybatis-plus.type-enums-package=com.qingtengcloud.utils.enums
|
bean对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class BTenant {
private Long id;
private String name;
private String mobile;
private GenderEnum gender;
private BTenantInfoTypeEnum infoType; ****get、set方法
|
枚举类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public enum GenderEnum { OTHER("0", "未知"), MAN("1", "男"), FEMALE("2", "女"); private String key; private String value; private static Map<String, GenderEnum> genderEnumMap = new HashMap<>(); static { for (GenderEnum sexEnum : GenderEnum.values()) { genderEnumMap.put(sexEnum.getKey(), sexEnum); } }
private GenderEnum(String key, String value) { this.key = key; this.value = value; }
public static GenderEnum getGenderEnumByKey(String key) { return genderEnumMap.get(key); } public String getKey() { return key; } public void setKey(String key) { this.key = key; } @JsonValue public String getValue() { return value; } public void setValue(String value) { this.value = value; } @JsonCreator public static GenderEnum create(String value){ try{ return GenderEnum.valueOf(value); }catch (IllegalArgumentException e){ for (GenderEnum gender : GenderEnum.values()) { try { if (gender.value.equals(Integer.parseInt(value))) { return gender; } }catch (NumberFormatException n) { if (gender.value.equals(value)) { return gender; } } } throw new IllegalArgumentException("No element matches "+value); } } }
|
枚举控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class GenderEnumHandler extends BaseTypeHandler<GenderEnum> {
@Override public void setNonNullParameter(PreparedStatement ps, int i, GenderEnum parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter.getKey()); }
@Override public GenderEnum getNullableResult(ResultSet rs, String columnName) throws SQLException { String key = rs.getString(columnName); if (rs.wasNull()) { return null; } else { return GenderEnum.getGenderEnumByKey(key); } }
@Override public GenderEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String key = rs.getString(columnIndex); if (rs.wasNull()) { return null; } else { return GenderEnum.getGenderEnumByKey(key); } }
@Override public GenderEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String key = cs.getString(columnIndex); if (cs.wasNull()) { return null; } else { return GenderEnum.getGenderEnumByKey(key); } } }
|