java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java枚举类型

一文详解Java项目中如何优雅的使用枚举类型

作者:赵侠客

枚举类型在开发中是很常见的,有非常多的应用场景,这篇文章我们就来学习一下项目中如何优雅的使用枚举类型,感兴趣的小伙伴可以跟随小编一起学习一下

前言

枚举类型在开发中是很常见的,有非常多的应用场景,如状态管理、类型分类、权限控制、配置管理、错误码管理、日志级别等。正确合理的使用枚举可以给我们带来非常多的好处:

虽然枚举有诸多的好处,但是使用枚举也给我们带来了一些困扰:

针对枚举存在的问题,本文介绍一种枚举从数据库-->后端代码-->前端代码-->页面和从页面-->前端代码-->后端代码-->数据库的自动转换方案,大大方便前后端使用枚举类型。

自动转换目标

我们以用户状态为例,用户有两种状态:禁用和启用

前端页面:前端页面显示用户状态时用“禁用、启用”;

前端代码:前端代码里处理用户状态时用:“ENABLE、DISABLE”或者用“0、1”;

后端代码:后端代码使用StatusEnum枚举类;

数据库:数据库存储用户状态时禁用存1、启用存0。 

我们的目标是让枚举在各个环境流转时全自动转换。

代码与数据库自动转换

第一步创建统一的枚举基类BaseEnum

public interface BaseEnum {
    int getCode();
    String getName();
    String getEnumName();
    static <T extends BaseEnum> T getInstance(Class<T> clazz, String value) {
        T[] constants = clazz.getEnumConstants();
        for (T t : constants) {
            if(StrUtil.isNumeric(value)){
                if (t.getCode() == Integer.parseInt(value)) {
                    return t;
                }
            }else {
                if (t.getEnumName().equals(value)) {
                    return t;
                }
            }
        }
        return null;
    }
}

第二步创建用户状态类StatusEnum实现BaseEnum接口

public enum StatusEnum implements BaseEnum {
    ENABLE(0,"启用"),
    DISABLE(1,"禁用");
    @EnumValue
    private int code;
    private String name;
    StatusEnum(int code, String name) {
        this.code = code;
        this.name=name;
    }
    @Override
    public int getCode() {
        return code;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public String getEnumName() {
        return this.name();
    }
}

BaseEnum主要有三个方法

如果使用MybatisPlus, 可以使用@EnumValue注解很方便的帮我们解决数据库与实体对象中枚举类型的相互转换,如果只使用的Mybatis可以自定义TypeHandler来解决数据库到JAVA枚举对象的自动转换。

第三步创建用户类User用户状态使用StatusEnum

@Data
@TableName("user")
public class User {
    private Long id;
    private String userName;
    private StatusEnum status;
}

前后端相互转换

当前端查询用户时,我们希望将枚举的三个属性都返回给前端,前端页面显示时取status.name代码中使用status.enum或者status.code

{
  "id": 3581209395268,
  "userName": "test2@8531.cn",
  "status": {
    "name": "禁用",
    "enum": "DISABLE",
    "code": 1
  }
}

为了达到将枚举序列化成一个json对象,我们需要自定义序列化器和反序列化器,以下以SpringBoot自带的Jackson为例:

public class BaseEnumSerializer extends StdSerializer<BaseEnum> {
    public BaseEnumSerializer() {
        this(null);
    }
    public BaseEnumSerializer(Class<BaseEnum> t) {
        super(t);
    }
    @Override
    public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        gen.writeStringField("name",value.getName());
        gen.writeStringField("enum",value.getEnumName());
        gen.writeNumberField("code",value.getCode());
        gen.writeEndObject();;
    }
}
public class BaseEnumDeserializer<T extends BaseEnum> extends StdDeserializer<T> {
    private Class<T> type;
    public BaseEnumDeserializer() {
        this(null);
    }
    public BaseEnumDeserializer(Class<T> vc) {
        super(vc);
        type = vc;
    }
    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        return BaseEnum.getInstance(type, p.getText());
    }
}

自定义Jackson序列化器与反序列化器只能解决数据类型为application/json格式的请求,当请求类型为application/x-www-form-urlencoded我们还需要自定义Spring消息转换器

public class NumBaseEnumConverterFactory implements ConverterFactory<Number, BaseEnum> {
    @Override
    public <T extends BaseEnum> Converter<Number, T> getConverter(Class<T> aClass) {
        return new NumberToEnumConverter<>(aClass);
    }
    private final class NumberToEnumConverter<T extends BaseEnum> implements Converter<Number, T> {
        private Class<T> enumType;
        public NumberToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }
        @Override
        public T convert(Number s) {
            return BaseEnum.getInstance(enumType,s.toString());
        }
    }
}

public class StrBaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
    @Override
    public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> aClass) {
        return new StringToEnumConverter<>(aClass);
    }
    private final class StringToEnumConverter<T extends BaseEnum> implements Converter<String, T> {
        private Class<T> enumType;
        public StringToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }
        @Override
        public T convert(String s) {
            return BaseEnum.getInstance(enumType,s);
        }
    }
}

以上两个消息转换器可以在数据格式以表单形式提交时将数值类型(0、1)和枚举值类型(ENABLE、DISABLE)转成枚举类型。

将自定义好的数据转换器注入到Spring中,这样就完成所有枚举自动转换。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(BaseEnum.class, new BaseEnumSerializer());
        module.addDeserializer(BaseEnum.class, new BaseEnumDeserializer<>());
        mapper.registerModule(module);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper;
    }
     @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(new StrBaseEnumConverterFactory());
        registry.addConverterFactory(new NumBaseEnumConverterFactory());
    }
}

查询用户

GET http://localhost:90/user/3581209395268
返回:
{
  "id": 3581209395268,
  "userName": "test2@8531.cn",
  "status": {
    "name": "启用",
    "enum": "ENABLE",
    "code": 0
  }
}

application/json格式传参

POST http://localhost:90/user
Content-Type: application/json

{
  "id": 3581209395268,
  "status": "DISABLE"
}

###
POST http://localhost:90/user
Content-Type: application/json

{
  "id": 3581209395268,
  "status": "0"
}

application/x-www-form-urlencoded格式传参

PUT http://localhost:90/user
Content-Type: application/x-www-form-urlencoded

id=3581209395268&status=ENABLE
###
PUT http://localhost:90/user
Content-Type: application/x-www-form-urlencoded

id=3581209395268&status=1

###
PUT http://localhost:90/user/3581209395268?status=ENABLE
Content-Type: application/x-www-form-urlencoded

###
PUT http://localhost:90/user/3581209395268?status=1
Content-Type: application/x-www-form-urlencoded

@PathVariable格式传参

PUT http://localhost:90/user/3581209395268/ENABLE
Content-Type: application/x-www-form-urlencoded

###
PUT http://localhost:90/user/3581209395268/1
Content-Type: application/x-www-form-urlencoded

对应JAVA代码:

@RestController
public class UserController {
    @Resource
    private UserMapper userMapper;

    @GetMapping("/user/{id}")
    public User getById(@PathVariable Long id) {
        return userMapper.selectById(id);
    }
    
    @PostMapping("/user")
    public User upadteById(@RequestBody User user) {
        userMapper.updateById(user);
        return user;
    }

    @PutMapping("/user")
    public User updateUser(User user) {
        userMapper.updateById(user);
        return user;
    }

    @PutMapping("/user/{id}/{status}")
    public User updateStatus(@PathVariable Long id,@PathVariable StatusEnum status) {
        User user=userMapper.selectById(id);
        user.setStatus(status);
        userMapper.updateById(user);
        return user;
    }

    @PutMapping("/user/{id}")
    public User updateUserStatus(@PathVariable Long id,@RequestParam StatusEnum status) {
        User user=userMapper.selectById(id);
        user.setStatus(status);
        userMapper.updateById(user);
        return user;
    }
}

这样很方便的解决了枚举在各个环节的自动转换问题,其它枚举只要实现BaseEnum接口就能实现全自动转换,前后端用起来也方便了不少。

总结

本文主要介绍了项目中使用枚举的优缺点,并针对缺点给出了解决方案,解决了枚举在项目中频繁转换的问题,当然解决的还不是非常完美,比如返回给前端的枚举格式是:{"enum":"DISABLE","code":1} 但是保存时传此数据结构,后端却无法正确的转成枚举,我们可以创建StatusEnumDeserializer,将子json对象转成对应枚举就好了,但是范型的写法目前还不知道怎么写,不可能增加一个枚举写一个反序列化器,有知道的可以回复一下,相互学习。

public class StatusEnumDeserializer   extends JsonDeserializer<StatusEnum>  {
    @Override
    public StatusEnum  deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonNode node= p.getCodec().readTree(p);
        if(node.isObject()){
            String name= node.get("enum").toString();
            return BaseEnum.getInstance(StatusEnum.class, name);
        }else {
            return BaseEnum.getInstance(StatusEnum.class, node.textValue());
        }
    }
}

以上就是一文详解Java项目中如何优雅的使用枚举类型的详细内容,更多关于Java枚举类型的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文