TECH NOTES / Java 与 Spring
Spring Boot 中的枚举使用
原笔记 2021-10-09 · 整理于 2026-10-05
让枚举参与 Spring Boot 的请求参数转换、JSON 序列化和数据库字段映射。
示例说明:代码包含请求转换、Jackson 和 MyBatis-Plus 等不同层的用法,枚举与业务类需配套使用;不是单文件即可运行的完整项目。
前后端交互,序列化与反序列化
序列化(将枚举对象序列化为json给前端)
推荐将枚举统一序列化为 json ,REST的风格接口都是序列化为json
直接在枚举类添加@JsonFormat***(***shape = JsonFormat.Shape.OBJECT) 注解即可
如下:
package com.leigq.www.mybatisplusstudy.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 性别枚举
* @author leiguoqing
*/
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@Getter
@AllArgsConstructor
public enum GenderEnum implements IEnum<Integer> {
/**
* 男
*/
MALE(1, "男"),
/**
* 女
*/
FEMALE(2, "女"),
/**
* 未知
*/
UNKNOWN(3, "未知"),
;
@EnumValue
private final Integer value;
private final String description;
}
返回值:
{
"code": 2000,
"msg": "请求/处理成功!",
"data": [
{
"id": 1446888414960676866,
"createdBy": 0,
"createdTime": 1633800055000,
"updatedBy": 0,
"updatedTime": 1633800055000,
"name": "jack",
"age": 10,
"gender": {
"value": 2,
"description": "女"
},
"workAge": 5,
"wages": 10000.00,
"hobbies": [
"唱歌",
"跳舞"
],
"version": 1,
"deleted": false,
"did": 1
},
{
"id": 1446888838371414018,
"createdBy": 0,
"createdTime": 1633800156000,
"updatedBy": 0,
"updatedTime": 1633800156000,
"name": "jack",
"age": 10,
"gender": {
"value": 2,
"description": "女"
},
"workAge": 5,
"wages": 10000.00,
"hobbies": [
"唱歌",
"跳舞"
],
"version": 1,
"deleted": false,
"did": 1
},
{
"id": 1446889136909377538,
"createdBy": 0,
"createdTime": 1633800227000,
"updatedBy": 0,
"updatedTime": 1633800227000,
"name": "jack",
"age": 10,
"gender": {
"value": 2,
"description": "女"
},
"workAge": 5,
"wages": 10000.00,
"hobbies": [
"唱歌",
"跳舞"
],
"version": 1,
"deleted": false,
"did": 1
},
{
"id": 1446889611947855873,
"createdBy": 0,
"createdTime": 1633800340000,
"updatedBy": 0,
"updatedTime": 1633800340000,
"name": "jack",
"age": 10,
"gender": {
"value": 2,
"description": "女"
},
"workAge": 5,
"wages": 10000.00,
"hobbies": [
"唱歌",
"跳舞"
],
"version": 1,
"deleted": false,
"did": 1
}
]
}反序列化(从前端接收枚举值反序列化为枚举对象)
需要分情况处理
普通请求
对于诸如Get请求,Post表单请求的普通请求,可以使用自定义的转换器工厂进行处理,我们需要继承ConverterFactory类并指定想要处理的类型。
自定义的转换器
package com.leigq.www.mybatisplusstudy.web.config;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.sun.istack.internal.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* String 类型参数转为 Enum 类型,适用 Get请求,Post表单请求的普通请求,不适用 POST json 请求
* <pre>
* IEnum 是 MybatisPlus 中的自定义枚举接口,也可以自己写一个
* 需要转换的枚举实现 com.baomidou.mybatisplus.annotation.IEnum 接口 或者 在需要转换的属性上加 @com.baomidou.mybatisplus.annotation.EnumValue 注解
* </pre>
* @author leiguoqing
*/
@Slf4j
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
/**
* The constant CONVERTER_MAP.
*/
private static final Map<Class<?>, Converter<String, ? extends Enum<?>>> CONVERTER_MAP = new ConcurrentHashMap<>();
/**
* The constant TABLE_METHOD_OF_ENUM_TYPES.
*/
private static final Map<Class<?>, Method> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
/**
* Gets converter.
*
* @param <T> the type parameter
* @param targetType the target type
* @return the converter
*/
@Override
@SuppressWarnings("unchecked cast")
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
// 缓存转换器
Converter<String, T> converter = (Converter<String, T>) CONVERTER_MAP.get(targetType);
if (converter == null) {
converter = new StringToEnumConverter<>(targetType);
CONVERTER_MAP.put(targetType, converter);
}
return converter;
}
/**
* The type String to enum converter.
*
* @param <T> the type parameter
*/
static class StringToEnumConverter<T extends Enum<?>> implements Converter<String, T> {
/**
* The Enum map.
*/
private final Map<String, T> enumMap = new ConcurrentHashMap<>();
/**
* Instantiates a new String to enum converter.
*
* @param enumType the enum type
*/
StringToEnumConverter(Class<T> enumType) {
Method method = getMethod(enumType);
T[] enums = enumType.getEnumConstants();
// 将值与枚举对象对应并缓存
for (T e : enums) {
try {
enumMap.put(method.invoke(e).toString(), e);
} catch (IllegalAccessException | InvocationTargetException ex) {
log.error("获取枚举值错误!!! ", ex);
}
}
}
/**
* Convert t.
*
* @param source the source
* @return the t
*/
@Override
public T convert(@NotNull String source) {
// 获取
T t = enumMap.get(source);
if (t == null) {
throw new IllegalArgumentException("该字符串找不到对应的枚举对象 字符串:" + source);
}
return t;
}
}
/**
* Gets method.
*
* @param <T> the type parameter
* @param enumType the enum type
* @return the method
*/
public static <T> Method getMethod(Class<T> enumType) {
Method method;
// 找到取值的方法
if (IEnum.class.isAssignableFrom(enumType)) {
try {
method = enumType.getMethod("getValue");
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(String.format("类:%s 找不到 getValue方法", enumType.getName()));
}
} else {
method = TABLE_METHOD_OF_ENUM_TYPES.computeIfAbsent(enumType, k -> {
Field field = dealEnumType(enumType)
.orElseThrow(
() -> new IllegalArgumentException(String.format("类:%s 找不到 EnumValue注解", enumType.getName()))
);
return ReflectionUtils.findMethod(enumType, "get" + StringUtils.capitalize(field.getName()));
});
}
return method;
}
/**
* Deal enum type optional.
*
* @param clazz the clazz
* @return the optional
*/
private static Optional<Field> dealEnumType(Class<?> clazz) {
return clazz.isEnum() ?
Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst() : Optional.empty();
}
}
使自定义的转换器工厂生效
需要实现WebMvcConfigurer接口并在addFormatters方法中进行追加。
package com.leigq.www.mybatisplusstudy.web.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* MVC配置
* <pre>
* 有两种方式配置,推荐使用 "实现 WebMvcConfigurer 接口 " 实现, 参考: https://blog.csdn.net/cp026la/article/details/86518655
* <code>@SpringBootConfiguration</code>继承自 <code>@Configuration</code>,二者功能也一致,标注当前类是配置类,
* 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。
* <code>@EnableWebMvc</code> 启用此注解,SpringBoot 关于 MVC 的自动装载会失效,交由开发者自定义
* </pre>
* @author leiguoqing
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* Add formatters.
*
* @param registry the registry
*/
@Override
public void addFormatters(FormatterRegistry registry) {
// 前端传 String 类型,后端自动转为枚举,需要转换的枚举实现 com.baomidou.mybatisplus.annotation.IEnum 接口 或者 在需要转换的属性上加 @com.baomidou.mybatisplus.annotation.EnumValue 注解
registry.addConverterFactory(new StringToEnumConverterFactory());
}
}
这样就可以了,接下来处理 json 请求。
看下效果
GET请求:


普通POST请求:


POST json 请求
json请求使用RequestBody注解标记接收,处理器为项目中指定的消息转换器。在Springboot中默认为Jackson。
Jackson对枚举的默认行为为按枚举名或其所在的位置(从0开始计算),例如当传入0时获取的是枚举类中的第一个对象。
通用的解决方案,需要实现ContextualDeserializer辅助获取转换时的类信息(createContextual每个枚举类只会触发一次,之后都使用该方法返回的反序列化器处理)。
注意: 其中 StringToEnumConverterFactory.getMethod(clazz) 调用的是上面那个类中的方法
package com.leigq.www.mybatisplusstudy.web.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* jackson 枚举类型反序列化,从前端接收枚举值反序列化为枚举对象,适用 POST json 请求,不适用 Get请求,Post表单请求的普通请求
* <pre>
* IEnum 是 MybatisPlus 中的自定义枚举接口,也可以自己写一个
* 需要转换的枚举实现 com.baomidou.mybatisplus.annotation.IEnum 接口 或者 在需要转换的属性上加 @com.baomidou.mybatisplus.annotation.EnumValue 注解
* </pre>
*
* @author leiguoqing
*/
@Slf4j
@Setter
public class JacksonEnumDeserializer extends JsonDeserializer<Enum<?>> implements ContextualDeserializer {
/**
* The Clazz.
*/
private Class<?> clazz;
/**
* Deserialize enum.
*
* @param jsonParser the json parser
* @param ctx the ctx
* @return the enum
* @throws IOException the io exception
*/
@Override
public Enum<?> deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
Class<?> enumType = clazz;
if (Objects.isNull(enumType) || !enumType.isEnum()) {
return null;
}
String text = jsonParser.getText();
Method method = StringToEnumConverterFactory.getMethod(clazz);
Enum<?>[] enumConstants = (Enum<?>[]) enumType.getEnumConstants();
// 将值与枚举对象对应并缓存
for (Enum<?> e : enumConstants) {
try {
if (Objects.equals(method.invoke(e).toString(), text)) {
return e;
}
} catch (IllegalAccessException | InvocationTargetException ex) {
log.error("获取枚举值错误!!! ", ex);
}
}
return null;
}
/**
* 为不同的枚举获取合适的解析器
*
* @param ctx ctx
* @param property property
* @return the json deserializer
*/
@Override
public JsonDeserializer<Enum<?>> createContextual(DeserializationContext ctx, BeanProperty property) {
Class<?> rawCls = ctx.getContextualType().getRawClass();
JacksonEnumDeserializer converter = new JacksonEnumDeserializer();
converter.setClazz(rawCls);
return converter;
}
}
局部配置
/**
* 性别
* <br/>
* {@link @JsonDeserialize}枚举类型反序列化局部配置,从前端接收枚举值反序列化为枚举对象,前端传 value 值
*/
@JsonDeserialize(using = JacksonEnumDeserializer.class)
private GenderEnum gender;
全局配置
全局配置了的话,就不用在枚举属性上面写 @JsonDeserialize***(using = JacksonEnumDeserializer.class)*** 注解了。
package com.leigq.www.mybatisplusstudy.web;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.leigq.basaltools.util.JacksonUtils;
import com.leigq.www.mybatisplusstudy.web.config.JacksonEnumDeserializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ObjectMapper配置,在使用 ObjectMapper 的地方,方便直接注入 ObjectMapper 进行使用,
* 但是推荐统一使用 {@link JacksonUtils} 工具类
*
* @author leiguoqing
* @date 2020 -07-22 21:03:08
*/
@Configuration
public class JacksonConfig {
/**
* jackson 2 ObjectMapper 构建定制器
* <br/>
* 该段代码并未覆盖SpringBoot自动装配的ObjectMapper对象,而是加强其配置.
* 详情请参考: <a href='https://www.jianshu.com/p/68fce8b23341'>SpringBoot2.x下的ObjectMapper配置原理</a>
*
* @return the jackson 2 object mapper builder customizer
* @author leiguoqing
* @date 2020 -07-22 21:23:18
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer customJackson() {
return jacksonObjectMapperBuilder -> {
// 枚举反序列化全局处理,局部配置使用 @JsonDeserialize(using = JacksonEnumDeserializer.class),详见 com.leigq.www.mybatisplusstudy.param.AddStaffsParam
// 序列化的话不做全局处理,在枚举对象上面使用 @JsonFormat(shape = JsonFormat.Shape.OBJECT) 注解即可
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Enum.class, new JacksonEnumDeserializer());
/* 注册模块 */
jacksonObjectMapperBuilder.modules(simpleModule);
};
}
}
看下效果


数据库交互,入库与出库
借助MyBatis-Plus可以很容易的实现这一点。
首先需要在配置文件中加入type-enums-package指定枚举的扫描包,MyBatis-Plus将为包内(包含子包)所有枚举进行适配,可以使用逗号或封号分隔多个包名。
mybatis-plus:
type-enums-package: [枚举包][,|;][枚举包]接着在枚举类中指定数据库值所对应的属性。这里可以采用两种方式。
方式一:
- 实现官方提供的IEnum接口,接口中的getValue方法与数据库值对应的属性。
@Getter//实现getValue
public enum StatusEnum implements IEnum<Integer> {
VALID(1, "有效"),
INVALID(0, "无效");
StatusEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
//标记数据库存的值是value
private final Integer value;
private final String desc;
}
方式二:
- 将属性使用EnumValue注解标记数据库值对应的属性。
@Getter//实现getValue
public enum StatusEnum {
VALID(1, "有效"),
INVALID(0, "无效");
StatusEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
//标记数据库存的值是value
@EnumValue
private final Integer value;
private final String desc;
}
在类的属性声明上直接将字段类型标记为枚举类型,读取时将自动转换数据库值为枚举对象。
@Data
@Accessors(chain = true)
@TableName("test")
public class TestDO {
private Integer id;
private String username;
private StatusEnum status;
}
参考: