虽然项目使用了枚举类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 extends IDictEnum> clazz;
public EnumJsonDeSerializer() {
}
public EnumJsonDeSerializer(Class extends IDictEnum> 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 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 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;
}
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
是不是很棒*_^