Spring Boot 使用枚举类型作为请求参数

https://xkcoding.com/2019/01/30/spring-boot-request-use-enums-params.html

1. 场景

在我们实际开发中,枚举类型应用十分广泛,可以避免在项目中定义大量的『魔法值』变量。但是,在 web 开发中,如何将枚举对象作为请求参数传进 Controller,做到类型自动转换?直接使用 @RequestParam 和 @RequestBody 断然是不够的,这里就需要我们自定义 Converter 来实现类型转化了。

2. 需求

比如一个用户对象,里面的性别属性,我们定义一个枚举类型 Gender

GenderEnum.java


/**
 * 

* 性别枚举 *

* * @package: com.xkcoding.springboottest.constants.enums * @description: 性别枚举 * @author: yangkai.shen * @date: Created in 2019-01-30 11:06 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ @Getter public enum GenderEnum { /** * 男 */ MALE(0), /** * 女 */ FEMALE(1); /** * 性别编码 */ private Integer code; GenderEnum(int code) { this.code = code; } }

请求对象封装 QueryRequest

QueryRequest.java


/**
 * 

* 请求参数 *

* * @package: com.xkcoding.springboottest.controller.enum_test * @description: 请求参数 * @author: yangkai.shen * @date: Created in 2019-01-30 14:02 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ @Data public class QueryRequest { private GenderEnum gender; }

在 Controller 层直接使用如下方式,期望参数自动进行类型转换

EnumTestController.java


/**
 * 

* 测试参数是枚举类型,自动转换 *

* * @package: com.xkcoding.springboottest.controller.enum_test * @description: 测试参数是枚举类型,自动转换 * @author: yangkai.shen * @date: Created in 2019-01-30 13:56 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ @Slf4j @RestController @RequestMapping("/enum") public class EnumTestController { @GetMapping("/get") public Dict testGet(QueryRequest request) { log.info("【get-request】= {}", JSONUtil.toJsonStr(request)); return Dict.create().set("get-request", request); } @PostMapping("/post") public Dict testPost(@RequestBody QueryRequest request) { log.info("【post-request】= {}", JSONUtil.toJsonStr(request)); return Dict.create().set("post-request", request); } }

这么写的时候,gender 只能接收到 MALE 、FEMALE 这样的参数,除此以外,均会报类型不匹配的错误信息,此时是无法处理 0 、1 这样的参数的。

需求:

  1. 接收到 MALE 、FEMALE 这样的参数,可以自动转为对应的枚举值;
  2. 接收到 0 、1 这样的参数,也可以自动转为对应的枚举值。

3. 解决

此时,本文的『主角』Converter 就可以隆重登场了。

注意,Converter 是 org.springframework.core.convert.converter.Converter ,别导错包了。

IntegerCodeToGenderEnumConverter.java


/**
 * 

* 编码 -> 性别 转换器 *

* * @package: com.xkcoding.springboottest.config.convert * @description: 编码 -> 性别 转换器 * @author: yangkai.shen * @date: Created in 2019-01-30 11:14 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ public class IntegerCodeToGenderEnumConverter implements Converter { private Map enumMap = Maps.newHashMap(); public IntegerCodeToGenderEnumConverter() { for (GenderEnum genderEnum : GenderEnum.values()) { enumMap.put(genderEnum.getCode(), genderEnum); } } @Override public GenderEnum convert(Integer source) { GenderEnum genderEnum = enumMap.get(source); if (ObjectUtil.isNull(genderEnum)) { throw new IllegalArgumentException("无法匹配对应的枚举类型"); } return genderEnum; } }

其实这里已经可以实现类型转换了,但是引出另外一个问题,当我们的枚举类特别多的时候,我们就需要写很多个 自定义的 Converter 来满足类型转化。

所以我们不使用上面直接使用 Converter 的这种方法,我们引入 ConverterFactory 来解决这个问题。

3.1. 抽取公共枚举接口

以后的枚举类都需要实现这个接口

BaseEnum.java


/**
 * 

* 通用枚举接口 *

* * @package: com.xkcoding.springboottest.constants * @description: 通用枚举接口 * @author: yangkai.shen * @date: Created in 2019-01-30 11:04 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ public interface BaseEnum { /** * 获取枚举编码 * * @return 编码 */ Integer getCode(); }

3.2. 调整 GenderEnum 枚举类

GenderEnum.java


/**
 * 

* 性别枚举 *

* * @package: com.xkcoding.springboottest.constants.enums * @description: 性别枚举 * @author: yangkai.shen * @date: Created in 2019-01-30 11:06 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ @Getter public enum GenderEnum implements BaseEnum { /** * 男 */ MALE(0), /** * 女 */ FEMALE(1); /** * 性别编码 */ private Integer code; GenderEnum(int code) { this.code = code; } }

3.3. 创建通用 Integer -> Enum 的 Converter 类

IntegerToEnumConverter.java


/**
 * 

* 枚举编码 -> 枚举 转化器 *

* * @package: com.xkcoding.springboottest.config.convert * @description: 枚举编码 -> 枚举 转化器 * @author: yangkai.shen * @date: Created in 2019-01-30 11:21 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ public class IntegerToEnumConverter implements Converter { private Map enumMap = Maps.newHashMap(); public IntegerToEnumConverter(Class enumType) { T[] enums = enumType.getEnumConstants(); for (T e : enums) { enumMap.put(e.getCode(), e); } } @Override public T convert(Integer source) { T t = enumMap.get(source); if (ObjectUtil.isNull(t)) { throw new IllegalArgumentException("无法匹配对应的枚举类型"); } return t; } }

3.4. 创建对应的自定义 ConverterFactory 工厂类

IntegerCodeToEnumConverterFactory.java


/**
 * 

* 编码 -> 枚举 转化器工厂类 *

* * @package: com.xkcoding.springboottest.config.convert.factory * @description: 编码 -> 枚举 转化器工厂类 * @author: yangkai.shen * @date: Created in 2019-01-30 11:09 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ public class IntegerCodeToEnumConverterFactory implements ConverterFactory { private static final Map CONVERTERS = Maps.newHashMap(); /** * 获取一个从 Integer 转化为 T 的转换器,T 是一个泛型,有多个实现 * * @param targetType 转换后的类型 * @return 返回一个转化器 */ @Override public Converter getConverter(Class targetType) { Converter converter = CONVERTERS.get(targetType); if (converter == null) { converter = new IntegerToEnumConverter<>(targetType); CONVERTERS.put(targetType, converter); } return converter; } }

3.5. 创建通用 String -> Enum 的 Converter 类和对应的 ConverterFactory 工厂类

因为 Post 请求可以对传入的 json 属性定义类型,但是 Get 请求后台接收到的参数都是 String 类型,因此需要在创建一个 Converter 类

StringToEnumConverter.java


/**
 * 

* 枚举编码 -> 枚举 转化器 *

* * @package: com.xkcoding.springboottest.config.convert * @description: 枚举编码 -> 枚举 转化器 * @author: yangkai.shen * @date: Created in 2019-01-30 11:21 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ public class StringToEnumConverter implements Converter { private Map enumMap = Maps.newHashMap(); public StringToEnumConverter(Class enumType) { T[] enums = enumType.getEnumConstants(); for (T e : enums) { enumMap.put(e.getCode().toString(), e); } } @Override public T convert(String source) { T t = enumMap.get(source); if (ObjectUtil.isNull(t)) { throw new IllegalArgumentException("无法匹配对应的枚举类型"); } return t; } }

StringCodeToEnumConverterFactory.java


/**
 * 

* 编码 -> 枚举 转化器工厂类 *

* * @package: com.xkcoding.springboottest.config.convert.factory * @description: 编码 -> 枚举 转化器工厂类 * @author: yangkai.shen * @date: Created in 2019-01-30 11:09 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ public class StringCodeToEnumConverterFactory implements ConverterFactory { private static final Map CONVERTERS = Maps.newHashMap(); /** * 获取一个从 Integer 转化为 T 的转换器,T 是一个泛型,有多个实现 * * @param targetType 转换后的类型 * @return 返回一个转化器 */ @Override public Converter getConverter(Class targetType) { Converter converter = CONVERTERS.get(targetType); if (converter == null) { converter = new StringToEnumConverter<>(targetType); CONVERTERS.put(targetType, converter); } return converter; } }

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

WebMvcConfig.java


/**
 * 

* MVC通用配置 *

* * @package: com.xkcoding.springboottest.config * @description: MVC通用配置 * @author: yangkai.shen * @date: Created in 2019-01-30 11:33 * @copyright: Copyright (c) 2019 * @version: V1.0 * @modified: yangkai.shen */ @Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 枚举类的转换器工厂 addConverterFactory */ @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new IntegerCodeToEnumConverterFactory()); registry.addConverterFactory(new StringCodeToEnumConverterFactory()); } }

这样我们就可以优雅的实现枚举对象参数的自动转换了,并且支持多个类型转换。其中的类型转换失败异常,可以使用全局异常拦截来处理,不会的小伙伴可以参考以前的文章。https://xkcoding.com/2018/08/20/spring-boot-global-exception-handler.html

5. 代码地址

https://github.com/xkcoding/owntest/tree/master/spring-boot-test


https://blog.csdn.net/alinyua/article/details/86383254

https://hpgary.iteye.com/blog/2426096

https://blog.wuwii.com/springboot-12.html

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java,Springboot)