SpringBoot 使用转换器将前端参数转换为枚举

开发 架构
最近遇到一个小伙伴问前端枚举转换问题,才意识到可以通过转换器(Converter)自动将前端传入的字段值使用枚举接收。

[[421671]]

前言

最近遇到一个小伙伴问前端枚举转换问题,才意识到可以通过转换器(Converter)自动将前端传入的字段值使用枚举接收。

我自己捣鼓了一番,现在记录笔记分享一下!有兴趣的小伙伴可以自己尝试一下!

这里使用的是 MyBatis-Plus 和 SpringBoot 2.3.4.RELEASE

1实现过程

配置转换器

  1. /** 
  2.  * @author liuzhihang 
  3.  * @date 2021/8/31 16:29 
  4.  */ 
  5. @Configuration 
  6. public class WebConfig implements WebMvcConfigurer { 
  7.  
  8.     @Override 
  9.     public void addFormatters(FormatterRegistry registry) { 
  10.  
  11.         registry.addConverterFactory(new ConverterFactory<Object, BaseEnum>() { 
  12.             @Override 
  13.             public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) { 
  14.  
  15.                 T[] enums = targetType.getEnumConstants(); 
  16.  
  17.                 return source -> { 
  18.  
  19.                     for (T e : enums) { 
  20.                         if (e.getCode().equals(source)) { 
  21.                             return e; 
  22.                         } 
  23.                     } 
  24.  
  25.                     throw new IllegalArgumentException("枚举 Code 不正确"); 
  26.                 }; 
  27.             } 
  28.         }); 
  29.     } 

直接在 WebMvcConfigurer 里实现 addFormatters 方法即可,然后 new 一个 ConverterFactory。

WebMvcConfigurer 相信大家都不陌生,一般添加一些拦截器,通用校验 token、日志等等都会用到。具体可以参考这篇文章:几行代码轻松实现跨系统传递 traceId,再也不用担心对不上日志了!,里面有一些其他的应用。

就这些,很简单的实现。下面介绍下项目的内容和代码,方便理解。

项目代码

  • 请求参数:
  1. POST http://localhost:8818/user/listByStatus 
  2. Content-Type: application/json 
  3.  
  4.   "orderStatus": 1 
  • Controller
  1. /** 
  2.  * @author liuzhihang 
  3.  * @date 2021/8/30 11:08 
  4.  */ 
  5. @Slf4j 
  6. @RestController 
  7. @RequestMapping("/user"
  8. public class UserController { 
  9.  
  10.     @Autowired 
  11.     private OrderService orderService; 
  12.  
  13.     @PostMapping(value = "/listByStatus"
  14.     public ResultVO<UserResponse> listByStatus(@Validated @RequestBody UserRequest request)  { 
  15.  
  16.         log.info("请求参数:{}", request); 
  17.  
  18.         List<TransOrder> orderList = orderService.getByOrderStatus(request.getOrderStatus()); 
  19.  
  20.         UserResponse response = new UserResponse(); 
  21.  
  22.         response.setRecords(orderList); 
  23.  
  24.         log.info("返回参数:{}", response); 
  25.  
  26.         return ResultVO.success(response); 
  27.     } 
  • Entity
  1. @Data 
  2. public class UserRequest { 
  3.  
  4.     private OrderStatusEnum orderStatus; 
  5.     private ViewStatusEnum viewStatus; 
  6.  
  7. @Data 
  8. public class UserResponse { 
  9.  
  10.     private List<TransOrder> records; 
  11.  

Web 传入 orderStatus 为 1,而后端接收对象是 UserRequest 的 orderStatus 字段是个 OrderStatusEnum 类型的枚举。

这里就需要自动将数字类型的字段转换为枚举字段。这个枚举会直接通过 MyBatis-Plus 查询。

为什么要这么用呢?

其实原因很简单,使用枚举限制数据库字段的类型,比如数据库状态只有 0、1、2,那就和代码里的枚举对应起来。防止传入其他值。

  • 枚举
  1. public interface BaseEnum { 
  2.     Object getCode(); 
  1. public enum OrderStatusEnum implements BaseEnum { 
  2.  
  3.     INIT(0, "初始状态"), 
  4.     SUCCESS(1, "成功"), 
  5.     FAIL(2, "失败"); 
  6.  
  7.     @EnumValue 
  8.     @JsonValue 
  9.     private final int code; 
  10.  
  11.     private final String desc
  12.  
  13.     OrderStatusEnum(int code, String desc) { 
  14.         this.code = code; 
  15.         this.desc = desc
  16.     } 
  17.  
  18.     @Override 
  19.     public Integer getCode() { 
  20.         return code; 
  21.     } 
  22.  
  23.     public String getDesc() { 
  24.         return desc
  25.     } 

这里先声明接口 BaseEnum,所有的枚举都继承这个接口,并实现 getCode 方法。

@EnumValue:MyBatis-Plus 的枚举,和数据库字段映射用的

@JsonValue:返回给前端时,这个枚举字段序列化时,返回参数只显示 code。

这样就可以实现效果,请求参数为数字,接收对象字段为枚举,返回字段也是 code。

效果

测试结果

测试结果经过验证,是可以胜任传入数值和字符串的。

也可以结合异常处理器,返回通用异常。具体怎么用查一查 @ExceptionHandler 就知道了。

具体说明

在 addFormatters 方法中可以看到 registry.addConverterFactory() 接收的是一个 ConverterFactory 对象。

  1. public interface ConverterFactory<S, R> { 
  2.  
  3.  <T extends R> Converter<S, T> getConverter(Class<T> targetType); 
  • S 就是传入的字段类型(数字,字符串)
  • R 是要转换为的类型(枚举)
  • T 继承了 R,其实就是参数对象中字段的类型

在 ConverterFactory 的 getConverter 方法则需要返回一个实际的转换器 Converter

  1. @FunctionalInterface 
  2. public interface Converter<S, T> { 
  3.  
  4.  @Nullable 
  5.  T convert(S source); 
  6.  

convert 方法的入参是一个 source,就是要转换为什么类型的,这里就是数字/字符串,然后返回一个枚举即可。

注意这里加了 @FunctionalInterface 就意味着这里是可以用 lambda 表达式的。

2优化

一般 WebConfig 中除了实现 addFormatters 方法外,还会实现 addInterceptors 等等,这样写难免会很长,所以可以改为下面这种。

  1. @Configuration 
  2. public class WebConfig implements WebMvcConfigurer { 
  3.  
  4.     @Autowired 
  5.     private LogInterceptor logInterceptor; 
  6.  
  7.     @Autowired 
  8.     private AppTokenInterceptor appTokenInterceptor; 
  9.  
  10.  
  11.     @Autowired 
  12.     private EnumConverterFactory enumConverterFactory; 
  13.  
  14.     @Override 
  15.     public void addInterceptors(InterceptorRegistry registry) { 
  16.  
  17.         // 日志 
  18.         registry.addInterceptor(logInterceptor) 
  19.                 .addPathPatterns("/**"); 
  20.  
  21.         // app token校验 
  22.         registry.addInterceptor(appTokenInterceptor) 
  23.                 .addPathPatterns("/app/**"); 
  24.  
  25.     } 
  26.  
  27.     @Override 
  28.     public void addFormatters(FormatterRegistry registry) { 
  29.          
  30.         // 枚举转换 
  31.         registry.addConverterFactory(enumConverterFactory); 
  32.     } 

这种就需要咱们创建 EnumConverterFactory 类并实现 ConverterFactory 接口了,还得注入到 Spring 容器中

  1. @Component 
  2. public class EnumConverterFactory implements ConverterFactory<Object, BaseEnum> { 
  3.  
  4.     @Override 
  5.     public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) { 
  6.  
  7.         return new EnumConverter<>(targetType); 
  8.     } 
  9. public class EnumConverter<T extends BaseEnum> implements Converter<Object, T> { 
  10.  
  11.     private final Class<T> targetType; 
  12.  
  13.     public EnumConverter(Class<T> targetType) { 
  14.         this.targetType = targetType; 
  15.     } 
  16.  
  17.     @Override 
  18.     public T convert(Object source) { 
  19.  
  20.         for (T e : targetType.getEnumConstants()) { 
  21.             if (e.getCode().equals(source)) { 
  22.                 return e; 
  23.             } 
  24.         } 
  25.  
  26.         throw new IllegalArgumentException("枚举 Code 不正确"); 
  27.     } 

3总结

当然这里也有一些其他的优化点,比如可以使用缓存将 Convert 缓存起来。

 

不过我也遇到一个其他的问题,就是我 debug 断点竟然一直没有断到转换器中,不知道有没有小伙伴尝试过?

 

责任编辑:武晓燕 来源: 程序员小航
相关推荐

2013-06-13 15:10:27

.NET代码转换

2010-06-10 14:44:33

协议转换器

2010-06-10 14:33:03

协议转换器

2010-06-10 14:38:30

协议转换器

2010-06-10 15:03:13

协议转换器

2014-05-04 12:51:21

Javascript编译器

2019-07-30 10:51:45

Markdown格式化文档Linux

2023-08-25 14:47:56

TransFLACFLAC

2023-05-05 00:19:22

2018-03-23 09:29:56

深度学习神经网络前端设计模型

2009-12-28 13:38:35

WPF类型转换器

2021-05-05 10:06:09

React应用程序微前端

2009-06-17 11:31:23

Open XMLUOF文档

2010-06-10 14:49:07

协议转换器

2009-09-11 12:41:41

C#类型转换

2023-08-26 16:06:10

COBOLJava数据

2009-07-15 16:56:59

Jython类型Java类型

2010-10-27 13:25:33

Oracle查询

2010-06-17 23:31:35

协议转换器

2022-11-01 16:20:56

Java图像文件图像文件类型
点赞
收藏

51CTO技术栈公众号