1. 场景

在controller 接收 包含有枚举类属性的类的时候,只能根据枚举类的成员名称去进行获取,不能根据成员变量去进行获取。根据成员变量是找不到对应的枚举成员

  • 正常情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 枚举类
@Getter
public enum LoginType implements NameValueEnum<String> {
MOBILE_CODE_LOGIN("0", "手机验证码登录"),
;
// 名称
private final String name;
// 值
private final String value;

LoginType(String value, String name) {
this.value = value;
this.name = name;
}
}

// 接收类
@Data
public class TestEnumsDTO {
LoginType loginType;
}

// Controller
@PostMapping("/enums")
public void testEnums(@RequestBody TestEnumsDTO test){
System.out.println(test);

}

// 测试
POST {{gateway}}/member/enums
Content-Type: application/json

{
"loginType":"MOBILE_CODE_LOGIN"
}

###
# 需求 我想要的情况 根据value 去查找对应的枚举成员
POST {{gateway}}/member/enums
Content-Type: application/json

{
"loginType":"0"
}

2. 需求

能根据枚举值的成员变量去进行获取对应的枚举成员而不是必须根据成员名去获取对应的成员

大致思路方向:

  1. 根据Spring提供的转换器去进行转换

3. 实现之Spring转换器

3.1 初步实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* <p>
* 登录类型枚举Value转换器
* Converter : org.springframework.core.convert.converter.Converter
* </p>
*
* @author: Star
* @Description: com.staro.train.member.core.converter
* @Version: 1.0
*/
public class ValueToLoginTypeEnumConverter implements Converter<String, LoginType> {
private Map<String, LoginType> enumMap = new HashMap<>();

/**
* 把LoginType 里的成员对应的成员变量value值存入hashmap中 初始化
*/
public ValueToLoginTypeEnumConverter() {
for (LoginType genderEnum : LoginType.values()) {
enumMap.put(genderEnum.getValue(), genderEnum);
}
}

/**
*
* @param source 目标字符串
* @return
*/
@Override
public LoginType convert(String source) {
LoginType loginTypeEnum = enumMap.get(source);
if (ObjectUtil.isNull(loginTypeEnum)) {
throw new IllegalArgumentException("无法匹配对应的枚举类型");
}
return loginTypeEnum;
}
}

3.2 通用实现

具体的实现思路是利用接口的向下转型

优点:

  • 如果用上一种方式,假如需要转换的枚举类型有很多那么将会写很多的转换类

缺点:

  • 成员变量名必须是value和name不然转换不会成功,需要转换的都必须实现NameValueEnum接口类

注意点:name和value是不能值一样的不然会出现歧义,在获取的过程中可能会进行覆盖

  • 定义两个接口 ValueEnum.javaNameValueEnum.java

  • ValueEnum.java

    1
    2
    3
    4
    5
    6
    7
    8
    public interface ValueEnum<T> {
    /**
    * 获取枚举值
    *
    * @return 枚举值
    */
    T getValue();
    }
  • NameValueEnum.java

    1
    2
    3
    4
    5
    6
    7
    8
    public interface NameValueEnum<T> extends ValueEnum<T>{
    /**
    * 获取枚举名称
    *
    * @return 枚举名
    */
    String getName();
    }
  • 所有需要转换的枚举需要实现这两个接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 枚举类 因为使用了lomok 所有并不会报错
    @Getter
    public enum LoginType implements NameValueEnum<String> implements NameValueEnum{
    MOBILE_CODE_LOGIN("0", "手机验证码登录"),
    ;
    // 名称
    private final String name;
    // 值
    private final String value;

    LoginType(String value, String name) {
    this.value = value;
    this.name = name;
    }
    }
  • 定义Converter .javaConverterFactory.java

    • Converter .java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class NameValueToEnumConverter<T extends NameValueEnumimplements Converter<String, T> {
    private Map<String, T> nameMap = new HashMap<>();
    private Map<String, T> valueMap = new HashMap<>();

    /**
    * 根据enumType 去获取枚举里面的成员 并存入map中
    */
    public NameValueToEnumConverter(Class<T> enumType) {
    // getEnumConstants 获取枚举成员 如果不是枚举返回null
    Arrays.stream(enumType.getEnumConstants())
    .forEach(o->{
    nameMap.put(o.getName(),o);
    valueMap.put(o.getValue().toString(),o);
    });
    }

        /**
    *
    * @param source 传进controller的枚举字符串
    * @return
    */
    @Override
    public T convert(String source) {
    T value = valueMap.get(source);
    if (!ObjectUtil.isNull(value)) {
    return value;
    }
    T name = nameMap.get(source);
    if (!ObjectUtil.isNull(name)) {
    return name;
    }
    throw new BizException(ResultCode.ENUM_PARAMS_NOT_MATCH);
    }
        }
    }
    • ConverterFactory.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class NameValueToEnumConverterFactory implements ConverterFactory<String, NameValueEnum> {
    // 存储转换器的缓存
    private static final Map<Class, Converter> CONVERTERS = new HashMap<>();

    /**
    * 获取转换器的方法
    * @param targetType 转换后的类型
    * @return 返回一个转化器
    */
    @Override
    public <T extends NameValueEnum> Converter<String, T> getConverter(Class<T> targetType) {
    // 从缓存中获取已存在的转换器
    Converter<String, T> converter = CONVERTERS.get(targetType);
    if (converter == null) {
    // 如果缓存中不存在该类型的转换器,则创建一个新的转换器 防止重复添加
    converter = new NameValueToEnumConverter<>(targetType);
    // 将新创建的转换器添加到缓存中
    CONVERTERS.put(targetType, converter);
    }
    return converter;
    }
    }

3.3 将转化器工厂添加进 Spring Boot 配置

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

/**
* 枚举类的转换器工厂 addConverterFactory
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new IntegerCodeToEnumConverterFactory());
registry.addConverterFactory(new StringCodeToEnumConverterFactory());
}
}

3.4 测试

测试通过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
###
POST {{gateway}}/member/member/enums
Content-Type: application/json

{
"loginType":"0"
}

###
POST {{gateway}}/member/member/enums2
Content-Type: application/x-www-form-urlencoded

loginType=0