使用枚举Enum代替int常量

一、问题

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

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

二、实现效果

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

import com.aa.docking.common.DataDictionary;
import lombok.Data;

@Data
public class DataReq {
    /**
     * 模式类型 {@link DataDictionary.ManageMode}
     */
    private Integer modeType;
}

/**
 * 数据字典
 */ 
public class DataDictionary {
   /**
    * 模式类型
    */
   public class ManageMode {
      /**
       * 模式一
       */ 
      public static final Integer MODE_1 = 1;
      /**
       * 模式二
       */
      public static final Integer MODE_2 = 2;
   }
}

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

import lombok.Data;
import com.aa.docking.module.amc.enums.outside.ManagementModeEnum;

@Data
public class DataReq {
    /**
     * 模式类型 {@link ManagementModeEnum}
     */
    private Integer modeType;
}


import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;
@Getter
@Accessors(fluent = true)
@AllArgsConstructor
public enum ManagementModeEnum {
    MODE_1(1, "模式一"),
    MODE_2(2, "模式二"),

    ;

    /**
     * 字典码值
     */
    private Integer code;
    /**
     * 字典描述
     */
    private String desc;

}

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

import com.aa.docking.module.amc.enums.outside.ManagementModeEnum;
import lombok.Data;

@Data
public class DataReq {
    /**
     * 模式类型
     */
    private ManagementModeEnum modeType;
}

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

{
  "modeType": {
     "code": 1
  }
}

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

{
  "modeType": 1
}

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

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

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

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

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

package java.io;

public interface Serializable {
}

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

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

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

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

指定反序列化的方法:EnumJsonDeserializer

指定序列化的方法:EnumJsonSerializer

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

/**
 * @description: 数据字典公共接口
 */
@JsonDeserialize(using = EnumJsonDeSerializer.class)
@JsonSerialize(using = EnumJsonSerializer.class)
public interface IDictEnum {
    /**
     * 获得字典码值
     */
    Integer code();

    /**
     * 获得字典描述, 这里不能用name, 因为java.lang.Enum已经定义了name
     */
    String desc();

}

2、反序列化方法:EnumJsonDeserializer

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;

import java.io.IOException;
import java.util.Arrays;

/**
 * @description: 数据字典枚举反序列化
 * @see springboot项目中枚举类型的最佳实践
 */
public class EnumJsonDeSerializer extends JsonDeserializer implements ContextualDeserializer {

    private Class clazz;

    public EnumJsonDeSerializer() {
    }

    public EnumJsonDeSerializer(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public IDictEnum deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
        String param = jsonParser.getText();
        IDictEnum[] enumConstants = clazz.getEnumConstants();
        JsonStreamContext parsingContext = jsonParser.getParsingContext();
        IDictEnum iDictEnum = Arrays.stream(enumConstants)
                .filter(x -> {
                    //x.toString(),取枚举的具体值,如:xxx.enums.share.DelFlagEnum 枚举里的“NOT_DELETE”
                    //从而使得两种形式都能识别
                    String enumCodeStr = x.toString();
                    return enumCodeStr.equals(param) || param.equals(x.code() + "");
                })
                .findFirst()
                .orElse(null);
        /*if (null == iEnum) {
            String msg = String.format("枚举类型%s从%s未能转换成功", clazz.toString(), param);
            throw new Exception(msg);
        }*/
        return iDictEnum;
    }

    @Override
    public Class handledType() {
        return IDictEnum.class;
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        JavaType type = property.getType();
        // 如果是容器,则返回容器内部枚举类型
        while (type.isContainerType()) {
            type = type.getContentType();
        }
        return new EnumJsonDeSerializer((Class) type.getRawClass());
    }
}

3、序列化方法:EnumJsonSerializer

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

/**
 * @description: 数据字典枚举序列化
 * @see springboot项目中枚举类型的最佳实践
 */
public class EnumJsonSerializer extends JsonSerializer {

    @Override
    public void serialize(IDictEnum iDictEnum, JsonGenerator generator, SerializerProvider provider) throws IOException {
        // 序列化只要code的值
        generator.writeNumber(iDictEnum.code());
        // 序列化形式: {"code": "", "desc": ""}
        //generator.writeStartObject();
        //generator.writeNumberField("code", iBaseDict.code());
        //generator.writeStringField("desc", iBaseDict.desc());
        //generator.writeEndObject();
    }

    @Override
    public Class handledType() {
        return IDictEnum.class;
    }
}

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

import com.aa.docking.base.common.enums.IDictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;

@Getter
@Accessors(fluent = true)
@AllArgsConstructor
public enum ManagementModeEnum implements IDictEnum {
    MODE_1(1, "模式一"),
    MODE_2(2, "模式二"),

    ;

    /**
     * 字典码值
     */
    private Integer code;
    /**
     * 字典描述
     */
    private String desc;

}

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

@Data
public class NeighInfoPageReq {
    private ManagementModeEnum modeType;
    private List modeTypeList;
    private Map map1;
}

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

{
    "modeType": 1,
    "modeTypeList": [1, 2]
    "map1": {
      "模式一", 1,
      "模式二", 2
    }
}

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

import lombok.Data;
import javax.validation.constraints.NotNull;

@Data
public class NeighInfoPageReq {
    /**
     * 模式类型
     */
    @NotNull(message = "模式不能为空或码值不正确")
    private ManagementModeEnum modeType;
}

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

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

package com.aa.docking.base.common.enums;

import com.aa.docking.base.common.util.EnumUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @description: mybatis自定义枚举类型转换
 * @see springboot + mybatis 自定义枚举类型转换
 */
@MappedTypes({IDictEnum.class})
public class EnumTypeHandler & IDictEnum> extends BaseTypeHandler {
    private Class type;

    public EnumTypeHandler(Class type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null.");
        }
        this.type = type;
    }

    /**
     * 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, IDictEnum parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setInt(i, parameter.code());
    }

    /**
     * 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
     */
    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : codeOf(code);
    }

    /**
     * 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
     */
    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : codeOf(code);
    }

    /**
     * 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型
     */
    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : codeOf(code);
    }

    private E codeOf(int code) {
        try {
            return EnumUtil.getEnumByCode(type, code);
        } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + code + " to " + type.getSimpleName() + " by code value.", ex);
        }
    }
}

这里的EnumUtil:

import com.aa.docking.base.common.enums.IDictEnum;
import org.apache.commons.lang3.StringUtils;

/**
 * @description: 公共枚举方法
 * @see 枚举Enum使用范例-公共枚举方法
 */
public class EnumUtil {

    /**
     * 根据code获取枚举
     */
    public static  T getEnumByCode(Class tClass, Integer code) {
        if (code != null) {
            for (T t : tClass.getEnumConstants()) {
                if (t.code().equals(code)) {
                    return t;
                }
            }
        }
        return null;
    }

    /**
     * 根据desc获取枚举
     */
    public static  T getEnumByDesc(Class tClass, String desc) {
        if (StringUtils.isNotBlank(desc)) {
            for (T t : tClass.getEnumConstants()) {
                if (t.desc().equals(desc)) {
                    return t;
                }
            }
        }
        return null;
    }

    /**
     * 根据code获取desc
     */
    public static  String getDescByCode(Class tClass, Integer code) {
        T t = getEnumByCode(tClass, code);
        if (null != t) {
            return t.desc();
        }
        return null;
    }

    /**
     * 根据desc获取code
     */
    public static  Integer getCodeByDesc(Class tClass, String desc) {
        T t = getEnumByDesc(tClass, desc);
        if (null != t) {
            return t.code();
        }
        return null;
    }
}

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

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

是不是很棒*_^

你可能感兴趣的:(java,枚举,序列化,反序列化)