一、问题
虽然项目使用了枚举Enum,来替代原来的Constant常量类,但DTO对象的入参仍然是Integer类型,顶多再加个 {@link } 说明它是对应哪个枚举。
但这仍然无法限制传入的值不会乱传。要是有人不写{@link } 或者{@link } 写错,那更死。
二、什么是序列化和反序列化
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 <a href="https://blog.51cto.com/u_15782374/5666254">springboot项目中枚举类型的最佳实践</a>*/
public class EnumJsonDeSerializer extends JsonDeserializer<IDictEnum> implements ContextualDeserializer {private Class<? extends IDictEnum> clazz;public EnumJsonDeSerializer() {}public EnumJsonDeSerializer(Class<? extends IDictEnum> clazz) {this.clazz = clazz;}@Overridepublic 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;}@Overridepublic Class<?> handledType() {return IDictEnum.class;}@SuppressWarnings({"unchecked"})@Overridepublic JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)throws JsonMappingException {JavaType type = property.getType();// 如果是容器,则返回容器内部枚举类型while (type.isContainerType()) {type = type.getContentType();}return new EnumJsonDeSerializer((Class<? extends IDictEnum>) 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 <a href="https://blog.51cto.com/u_15782374/5666254">springboot项目中枚举类型的最佳实践</a>*/
public class EnumJsonSerializer extends JsonSerializer<IDictEnum> {@Overridepublic 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();}@Overridepublic 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 {UNIFIED_AUTHORIZATION_MANAGEMENT( 1, "模式一"),SEMI_AUTHORIZED_MANAGEMENT(2, "模式二"),INDEPENDENT_MANAGEMENT(3, "模式三"),;/*** 字典码值*/private Integer code;/*** 字典描述*/private String desc;}
5、经验证以下几种写法都可以成功识别:
@Data
public class NeighInfoPageReq {private ManagementModeDictEnum managementMode;private List<ManagementModeDictEnum> managementModeList;private NeighInfoPageReq amcNeighInfoPageReq;private Map<Integer, ManagementModeDictEnum> map1;private Map<ManagementModeDictEnum, Integer> map2;
}
使用postman传参, 传Integer类型就行
{
“managementMode”: 1,
"managementModeList": [1, 2, 3]
}
6、假如枚举值传错了要怎么判断呢?加上validation的@NotNull即可。此时如果传“managementMode”: 4 就会报错。
import lombok.Data;
import javax.validation.constraints.NotNull;@Data
public class NeighInfoPageReq {@NotNull(message = "模式不能为空或码值不正确")private ManagementModeDictEnum managementMode;
}
四、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 <a href="https://blog.csdn.net/weixin_30273931/article/details/94930515">springboot + mybatis 自定义枚举类型转换</a>*/
@MappedTypes({IDictEnum.class})
public class EnumTypeHandler<E extends Enum<?> & IDictEnum> extends BaseTypeHandler<IDictEnum> {private Class<E> type;public EnumTypeHandler(Class<E> type) {if (type == null) {throw new IllegalArgumentException("Type argument cannot be null.");}this.type = type;}/*** 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型*/@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, IDictEnum parameter, JdbcType jdbcType)throws SQLException {ps.setInt(i, parameter.code());}/*** 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型*/@Overridepublic E getNullableResult(ResultSet rs, String columnName) throws SQLException {int code = rs.getInt(columnName);return rs.wasNull() ? null : codeOf(code);}/*** 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型*/@Overridepublic E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {int code = rs.getInt(columnIndex);return rs.wasNull() ? null : codeOf(code);}/*** 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型*/@Overridepublic 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 <a href="https://blog.csdn.net/qq_37953002/article/details/120352298">枚举Enum使用范例-公共枚举方法</a>*/
public class EnumUtil {/*** 根据code获取枚举*/public static <T extends IDictEnum> T getEnumByCode(Class<T> tClass, Integer code) {if (code != null) {for (T t : tClass.getEnumConstants()) {if (t.code().equals(code)) {return t;}}}return null;}/*** 根据desc获取枚举*/public static <T extends IDictEnum> T getEnumByDesc(Class<T> 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 <T extends IDictEnum> String getDescByCode(Class<T> tClass, Integer code) {T t = getEnumByCode(tClass, code);if (null != t) {return t.desc();}return null;}/*** 根据desc获取code*/public static <T extends IDictEnum> Integer getCodeByDesc(Class<T> 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