• 使用枚举Enum代替Integer类型


    一、问题

    虽然项目使用了枚举类Enum,来替代原来的Constant常量类,但Java对象的入参仍然是Integer类型,顶多再加个 {@link } 说明它是对应哪个枚举。

    但这仍然无法限制传入的值不会乱传。要是有人不写{@link } 或者{@link } 写错,那更死。

    二、实现效果

    1、最开始使用常量类的写法:(我觉得这种写法最烦的一点就是,如果常量类定义了Integer类型,就没法使用switch case来做判断)

    1. import com.aa.docking.common.DataDictionary;
    2. import lombok.Data;
    3. @Data
    4. public class DataReq {
    5. /**
    6. * 模式类型 {@link DataDictionary.ManageMode}
    7. */
    8. private Integer modeType;
    9. }
    10. /**
    11. * 数据字典
    12. */
    13. public class DataDictionary {
    14. /**
    15. * 模式类型
    16. */
    17. public class ManageMode {
    18. /**
    19. * 模式一
    20. */
    21. public static final Integer MODE_1 = 1;
    22. /**
    23. * 模式二
    24. */
    25. public static final Integer MODE_2 = 2;
    26. }
    27. }

    2、改进以后使用枚举的写法:

    1. import lombok.Data;
    2. import com.aa.docking.module.amc.enums.outside.ManagementModeEnum;
    3. @Data
    4. public class DataReq {
    5. /**
    6. * 模式类型 {@link ManagementModeEnum}
    7. */
    8. private Integer modeType;
    9. }
    10. import lombok.AllArgsConstructor;
    11. import lombok.Getter;
    12. import lombok.experimental.Accessors;
    13. @Getter
    14. @Accessors(fluent = true)
    15. @AllArgsConstructor
    16. public enum ManagementModeEnum {
    17. MODE_1(1, "模式一"),
    18. MODE_2(2, "模式二"),
    19. ;
    20. /**
    21. * 字典码值
    22. */
    23. private Integer code;
    24. /**
    25. * 字典描述
    26. */
    27. private String desc;
    28. }

    3、改进以后使用枚举代替Integer类型的写法:

    1. import com.aa.docking.module.amc.enums.outside.ManagementModeEnum;
    2. import lombok.Data;
    3. @Data
    4. public class DataReq {
    5. /**
    6. * 模式类型
    7. */
    8. private ManagementModeEnum modeType;
    9. }

    但存在一个问题,传参的时候需要传入一个枚举对象,否则匹配不到:

    1. {
    2. "modeType": {
    3. "code": 1
    4. }
    5. }

    我想要像原来一样直接传Integer类型就能匹配到枚举类:

    1. {
    2. "modeType": 1
    3. }

    要怎么实现呢?继续往下看:

    三、什么是序列化和反序列化

    1)序列化过程:把一个 Java 对象变成二进制内容(byte[]数组)存储起来。这里的存储方式可能是存储到磁盘中,也可能是发布到网络中。

    一个 Java 对象要能序列化,必须实现一个特殊的java.io.Serializable接口。

    Serializable 没有定义任何方法,它是一个空接口。这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

    1. package java.io;
    2. public interface Serializable {
    3. }

    2) 反序列化过程:把一个二进制内容(byte[]数组)变回 Java 对象。

    有了反序列化,保存到磁盘中的 byte[] 又可以“变回” Java 对象,或者从网络上读取 byte[] 并把它“变回” Java 对象。

    四、自定义序列化和反序列规则

    1、首先定义接口IDictEnum(所有枚举类都要去实现该接口)

    指定反序列化的方法:EnumJsonDeserializer

    指定序列化的方法:EnumJsonSerializer

    1. import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    2. import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    3. /**
    4. * @description: 数据字典公共接口
    5. */
    6. @JsonDeserialize(using = EnumJsonDeSerializer.class)
    7. @JsonSerialize(using = EnumJsonSerializer.class)
    8. public interface IDictEnum {
    9. /**
    10. * 获得字典码值
    11. */
    12. Integer code();
    13. /**
    14. * 获得字典描述, 这里不能用name, 因为java.lang.Enum已经定义了name
    15. */
    16. String desc();
    17. }

    2、反序列化方法:EnumJsonDeserializer

    1. import com.fasterxml.jackson.core.JsonParser;
    2. import com.fasterxml.jackson.core.JsonStreamContext;
    3. import com.fasterxml.jackson.databind.*;
    4. import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
    5. import java.io.IOException;
    6. import java.util.Arrays;
    7. /**
    8. * @description: 数据字典枚举反序列化
    9. */
    10. public class EnumJsonDeSerializer extends JsonDeserializer implements ContextualDeserializer {
    11. private Classextends IDictEnum> clazz;
    12. public EnumJsonDeSerializer() {
    13. }
    14. public EnumJsonDeSerializer(Class clazz) {
    15. this.clazz = clazz;
    16. }
    17. @Override
    18. public IDictEnum deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
    19. String param = jsonParser.getText();
    20. IDictEnum[] enumConstants = clazz.getEnumConstants();
    21. JsonStreamContext parsingContext = jsonParser.getParsingContext();
    22. IDictEnum iDictEnum = Arrays.stream(enumConstants)
    23. .filter(x -> {
    24. //x.toString(),取枚举的具体值,如:xxx.enums.share.DelFlagEnum 枚举里的“NOT_DELETE”
    25. //从而使得两种形式都能识别
    26. String enumCodeStr = x.toString();
    27. return enumCodeStr.equals(param) || param.equals(x.code() + "");
    28. })
    29. .findFirst()
    30. .orElse(null);
    31. /*if (null == iEnum) {
    32. String msg = String.format("枚举类型%s从%s未能转换成功", clazz.toString(), param);
    33. throw new Exception(msg);
    34. }*/
    35. return iDictEnum;
    36. }
    37. @Override
    38. public Class handledType() {
    39. return IDictEnum.class;
    40. }
    41. @SuppressWarnings({"unchecked"})
    42. @Override
    43. public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property)
    44. throws JsonMappingException {
    45. JavaType type = property.getType();
    46. // 如果是容器,则返回容器内部枚举类型
    47. while (type.isContainerType()) {
    48. type = type.getContentType();
    49. }
    50. return new EnumJsonDeSerializer((Classextends IDictEnum>) type.getRawClass());
    51. }
    52. }

    3、序列化方法:EnumJsonSerializer

    1. import com.fasterxml.jackson.core.JsonGenerator;
    2. import com.fasterxml.jackson.databind.JsonSerializer;
    3. import com.fasterxml.jackson.databind.SerializerProvider;
    4. import java.io.IOException;
    5. /**
    6. * @description: 数据字典枚举序列化
    7. */
    8. public class EnumJsonSerializer extends JsonSerializer {
    9. @Override
    10. public void serialize(IDictEnum iDictEnum, JsonGenerator generator, SerializerProvider provider) throws IOException {
    11. // 序列化只要code的值
    12. generator.writeNumber(iDictEnum.code());
    13. // 序列化形式: {"code": "", "desc": ""}
    14. //generator.writeStartObject();
    15. //generator.writeNumberField("code", iBaseDict.code());
    16. //generator.writeStringField("desc", iBaseDict.desc());
    17. //generator.writeEndObject();
    18. }
    19. @Override
    20. public Class handledType() {
    21. return IDictEnum.class;
    22. }
    23. }

    4、我们前面创建的枚举类,需要去实现 IDictEnum接口

    1. import com.aa.docking.base.common.enums.IDictEnum;
    2. import lombok.AllArgsConstructor;
    3. import lombok.Getter;
    4. import lombok.experimental.Accessors;
    5. @Getter
    6. @Accessors(fluent = true)
    7. @AllArgsConstructor
    8. public enum ManagementModeEnum implements IDictEnum {
    9. MODE_1(1, "模式一"),
    10. MODE_2(2, "模式二"),
    11. ;
    12. /**
    13. * 字典码值
    14. */
    15. private Integer code;
    16. /**
    17. * 字典描述
    18. */
    19. private String desc;
    20. }

    5、这样就搞定啦!!经验证,还支持List,Map等复杂的传参形式哦!!

    1. @Data
    2. public class NeighInfoPageReq {
    3. private ManagementModeEnum modeType;
    4. private List modeTypeList;
    5. private Map map1;
    6. }

    传参只要带Integer类型就能匹配到枚举类啦^_^

    1. {
    2. "modeType": 1,
    3. "modeTypeList": [1, 2]
    4. "map1": {
    5. "模式一", 1,
    6. "模式二", 2
    7. }
    8. }

    6、假如枚举值传错了要怎么判断呢?加上validation的@NotNull就可以啦。此时如果传“managementMode”: 3 就会报错。

    1. import lombok.Data;
    2. import javax.validation.constraints.NotNull;
    3. @Data
    4. public class NeighInfoPageReq {
    5. /**
    6. * 模式类型
    7. */
    8. @NotNull(message = "模式不能为空或码值不正确")
    9. private ManagementModeEnum modeType;
    10. }

    五、mybatis枚举序列化和反序列化处理

    1、mybatis有自己的一套序列化反序列化规则,所以我们还需要使用@MappedTypes,单独针对IDictEnum定义一套规则。

    1. package com.aa.docking.base.common.enums;
    2. import com.aa.docking.base.common.util.EnumUtil;
    3. import org.apache.ibatis.type.BaseTypeHandler;
    4. import org.apache.ibatis.type.JdbcType;
    5. import org.apache.ibatis.type.MappedTypes;
    6. import java.sql.CallableStatement;
    7. import java.sql.PreparedStatement;
    8. import java.sql.ResultSet;
    9. import java.sql.SQLException;
    10. /**
    11. * @description: mybatis自定义枚举类型转换
    12. */
    13. @MappedTypes({IDictEnum.class})
    14. public class EnumTypeHandlerextends Enum & IDictEnum> extends BaseTypeHandler {
    15. private Class type;
    16. public EnumTypeHandler(Class type) {
    17. if (type == null) {
    18. throw new IllegalArgumentException("Type argument cannot be null.");
    19. }
    20. this.type = type;
    21. }
    22. /**
    23. * 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
    24. */
    25. @Override
    26. public void setNonNullParameter(PreparedStatement ps, int i, IDictEnum parameter, JdbcType jdbcType)
    27. throws SQLException {
    28. ps.setInt(i, parameter.code());
    29. }
    30. /**
    31. * 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
    32. */
    33. @Override
    34. public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    35. int code = rs.getInt(columnName);
    36. return rs.wasNull() ? null : codeOf(code);
    37. }
    38. /**
    39. * 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
    40. */
    41. @Override
    42. public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    43. int code = rs.getInt(columnIndex);
    44. return rs.wasNull() ? null : codeOf(code);
    45. }
    46. /**
    47. * 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型
    48. */
    49. @Override
    50. public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    51. int code = cs.getInt(columnIndex);
    52. return cs.wasNull() ? null : codeOf(code);
    53. }
    54. private E codeOf(int code) {
    55. try {
    56. return EnumUtil.getEnumByCode(type, code);
    57. } catch (Exception ex) {
    58. throw new IllegalArgumentException("Cannot convert " + code + " to " + type.getSimpleName() + " by code value.", ex);
    59. }
    60. }
    61. }

    这里的EnumUtil:

    1. import com.aa.docking.base.common.enums.IDictEnum;
    2. import org.apache.commons.lang3.StringUtils;
    3. /**
    4. * @description: 公共枚举方法
    5. */
    6. public class EnumUtil {
    7. /**
    8. * 根据code获取枚举
    9. */
    10. public static extends IDictEnum> T getEnumByCode(Class tClass, Integer code) {
    11. if (code != null) {
    12. for (T t : tClass.getEnumConstants()) {
    13. if (t.code().equals(code)) {
    14. return t;
    15. }
    16. }
    17. }
    18. return null;
    19. }
    20. /**
    21. * 根据desc获取枚举
    22. */
    23. public static extends IDictEnum> T getEnumByDesc(Class tClass, String desc) {
    24. if (StringUtils.isNotBlank(desc)) {
    25. for (T t : tClass.getEnumConstants()) {
    26. if (t.desc().equals(desc)) {
    27. return t;
    28. }
    29. }
    30. }
    31. return null;
    32. }
    33. /**
    34. * 根据code获取desc
    35. */
    36. public static extends IDictEnum> String getDescByCode(Class tClass, Integer code) {
    37. T t = getEnumByCode(tClass, code);
    38. if (null != t) {
    39. return t.desc();
    40. }
    41. return null;
    42. }
    43. /**
    44. * 根据desc获取code
    45. */
    46. public static extends IDictEnum> Integer getCodeByDesc(Class tClass, String desc) {
    47. T t = getEnumByDesc(tClass, desc);
    48. if (null != t) {
    49. return t.code();
    50. }
    51. return null;
    52. }
    53. }

    2、配置文件里面还需要定义mybatis的typeHandler扫描包路径:

    1. # mybatis配置参数
    2. mybatis:
    3. # 定义typeHandler扫描包路径
    4. type-handlers-package: com.aa.docking

    是不是很棒*_^

  • 相关阅读:
    【C++】C++中的IO流
    Linux系统调优详解(四)——内存状态查看命令
    Java编程练习题Demo61-Demo70
    31.JavaScript数组进阶,一网打尽数组操作函数slice、filter、map、reduce、some、every、find、splice
    论如何自研电动车控制器
    打击勒索病毒:防御.kat6.l6st6r勒索病毒的最佳策略
    李沐:机器学习者进阶学习建议
    中国生态功能保护区shp数据
    C语言动态内存管理、柔性数组(超详细版)
    Flutter-无限循环滚动标签
  • 原文地址:https://blog.csdn.net/why_su/article/details/133739639