在日常的项目开发中经常会遇到一些取值范围固定的字段,例如性别、证件类型、会员等级等,此时我们可以利用枚举来最大程度减少字段的乱定义,统一管理枚举的值。
SpringMVC中对于枚举也有默认的处理策略:
对于@RequestParam,Spring是通过ConverterFactory来处理的,大致处理策略是根据枚举名称或枚举下标来转换枚举。

对于@RequestBody,Spring是通过Jackson配置将json内的枚举值转换为对象的,大致处理策略同样是根据枚举名称或枚举下标来转换枚举。
在SpringMVC内对枚举的默认处理逻辑是根据枚举的类名或枚举下标来将请求参数转化为枚举对象,这显然不太灵活,因此我们需要调整枚举字段的处理逻辑。
我们可以自定义ConvertFactory来自定义枚举字段的转化策略。
定义BaseEnum接口,规定所有枚举都应该实现此接口
public interface BaseEnum<T> {
/**
* 获取枚举值
*/
T getCode();
/**
* 根据值获取对应的枚举
* @param enumTypeClazz 枚举类型类
* @param value 值
*/
static <T extends BaseEnum> T getEnumByCode(Class<T> enumTypeClazz, Object value) {
if (enumTypeClazz == null || value == null) {
return null;
}
Optional<T> optional = Arrays.stream(enumTypeClazz.getEnumConstants()).filter(e ->{
Object enumCode = e.getCode();
return Objects.equals(Convert.convert(enumCode.getClass(), value),enumCode);
}).findFirst();
//如果不存在则抛异常
return optional.orElseThrow( ()-> new RuntimeException("[" + enumTypeClazz.getSimpleName() + "]参数错误[" + value + "]"));
}
}
自定义ConverterFactory
@Component
public class EnumConverterFactory implements ConverterFactory<String, BaseEnum> {
@Override
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
return source -> BaseEnum.getEnumByCode(targetType, source);
}
}
注册ConverterFactory
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new EnumConverterFactory());
}
}
这样配置后请求参数就会自动转换为枚举了。
//枚举类
@AllArgsConstructor
@Getter
public enum Gender implements BaseEnum<Integer> {
MALE(1,"男"),
FEMALE(2,"女"),
;
@EnumValue
private Integer code;
private String value;
}
//通过接口接受gender参数,能够根据code自动转换为对应的枚举
@GetMapping("/test")
public Gender insert(Gender gender) {
return gender;
}
RequestBody是通过Jackson转换对请求参数进行处理的,因此我们只需要自定义反序列化类即可
自定义序列化规则设置json内的值如何转换为枚举
public class EnumDeserializer extends JsonDeserializer<BaseEnum> {
/**
* 根据参数值获取对应的枚举
* @throws IOException
* @throws JacksonException
*/
@Override
public BaseEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
// 当前值
final String paramValue = p.getText();
//获取序列化信息
final JsonStreamContext parsingContext = p.getParsingContext();
// 获取当前序列化的类的对象
final Object currentValue = parsingContext.getCurrentValue();
//获取当前序列化的字段名
final String currentName = parsingContext.getCurrentName();
try {
// 反射获取当前序列化字段信息
final Field declaredField = currentValue.getClass().getDeclaredField(currentName);
// 通过字段信息获取对应的枚举的Class
final Class<BaseEnum> targetType = (Class<BaseEnum>) declaredField.getType();
//根据参数值获取对应的枚举
BaseEnum baseEnum = BaseEnum.getEnumByCode(targetType, paramValue);
if (ObjectUtil.isEmpty(baseEnum)) {
throw new RuntimeException("[" + currentName + "]参数错误");
}
//返回枚举
return baseEnum;
} catch (NoSuchFieldException e) {
throw new RuntimeException("[" + currentName + "]参数错误");
}
}
}
//可以直接加到刚刚定义的BaseEnum接口上,这样所有枚举就自动继承了
@JsonDeserialize(using = EnumDeserializer.class)
public interface BaseEnum<T> {
……
}
@PostMapping("/save")
public User save(@RequestBody User user) {
studentService.save(user);
return user;
}
如果我们返回的对象内有枚举字段,SpringMVC会默认将枚举的名称作为值返回,如果我们想指定枚举类的某个属性作为值,可以通过@JsonValue指定
@AllArgsConstructor
@Getter
public enum Gender implements BaseEnum<Integer> {
MALE(1,"男"),
FEMALE(2,"女"),
;
//指定转json时使用code作为值
@JsonValue
private Integer code;
private String value;
}
或者直接在枚举类上加@JsonFormat,将枚举转换为对象格式
@JsonFormat(shape= JsonFormat.Shape.OBJECT)
@JsonDeserialize(using = EnumDeserializer.class)
public interface BaseEnum<T> {
……
}
MybatisPlus直接在枚举类的属性上加@EnumValue即可,并且兼容xml内的动态sql
@AllArgsConstructor
@Getter
public enum Gender implements BaseEnum<Integer> {
MALE(1,"男"),
FEMALE(2,"女"),
;
//指定code作为入库时的值
@EnumValue
private Integer code;
private String value;
}