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

9eeff1e1e7b10180d1b8cfc8e92745c9.png

前言

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

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

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

1

实现过程

配置转换器
/**
 * @author liuzhihang
 * @date 2021/8/31 16:29
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {

        registry.addConverterFactory(new ConverterFactory<Object, BaseEnum>() {
            @Override
            public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) {

                T[] enums = targetType.getEnumConstants();

                return source -> {

                    for (T e : enums) {
                        if (e.getCode().equals(source)) {
                            return e;
                        }
                    }

                    throw new IllegalArgumentException("枚举 Code 不正确");
                };
            }
        });
    }
}

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

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

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

项目代码
  • 请求参数:

POST http://localhost:8818/user/listByStatus
Content-Type: application/json

{
  "orderStatus": 1
}
  • Controller

/**
 * @author liuzhihang
 * @date 2021/8/30 11:08
 */
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private OrderService orderService;

    @PostMapping(value = "/listByStatus")
    public ResultVO<UserResponse> listByStatus(@Validated @RequestBody UserRequest request)  {

        log.info("请求参数:{}", request);

        List<TransOrder> orderList = orderService.getByOrderStatus(request.getOrderStatus());

        UserResponse response = new UserResponse();

        response.setRecords(orderList);

        log.info("返回参数:{}", response);

        return ResultVO.success(response);
    }
}
  • Entity

@Data
public class UserRequest {

    private OrderStatusEnum orderStatus;
    private ViewStatusEnum viewStatus;
}

@Data
public class UserResponse {

    private List<TransOrder> records;

}

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

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

为什么要这么用呢?

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

  • 枚举

public interface BaseEnum {
    Object getCode();
}
public enum OrderStatusEnum implements BaseEnum {

    INIT(0, "初始状态"),
    SUCCESS(1, "成功"),
    FAIL(2, "失败");

    @EnumValue
    @JsonValue
    private final int code;

    private final String desc;

    OrderStatusEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

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

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

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

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

效果
a6b7af8ff11471c3cf86602ee145a8c6.png
测试结果

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

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

具体说明

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

public interface ConverterFactory<S, R> {

 <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
  1. S 就是传入的字段类型(数字,字符串)

  2. R 是要转换为的类型(枚举)

  3. T 继承了 R,其实就是参数对象中字段的类型

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

@FunctionalInterface
public interface Converter<S, T> {

 @Nullable
 T convert(S source);

}

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

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

2

优化

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

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LogInterceptor logInterceptor;

    @Autowired
    private AppTokenInterceptor appTokenInterceptor;


    @Autowired
    private EnumConverterFactory enumConverterFactory;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 日志
        registry.addInterceptor(logInterceptor)
                .addPathPatterns("/**");

        // app token校验
        registry.addInterceptor(appTokenInterceptor)
                .addPathPatterns("/app/**");

    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        
        // 枚举转换
        registry.addConverterFactory(enumConverterFactory);
    }
}

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

@Component
public class EnumConverterFactory implements ConverterFactory<Object, BaseEnum> {

    @Override
    public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) {

        T[] enums = targetType.getEnumConstants();

        return source -> {
            for (T e : enums) {
                if (e.getCode().equals(source)) {
                    return e;
                }
            }

            throw new IllegalArgumentException("枚举 Code 不正确");
        };
    }
}

要是实在觉得 lambda 看不惯,并且也不够优雅,那可以使用下面的方式。

@Component
public class EnumConverterFactory implements ConverterFactory<Object, BaseEnum> {

    @Override
    public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) {

        return new EnumConverter<>(targetType);
    }
}
public class EnumConverter<T extends BaseEnum> implements Converter<Object, T> {

    private final Class<T> targetType;

    public EnumConverter(Class<T> targetType) {
        this.targetType = targetType;
    }

    @Override
    public T convert(Object source) {

        for (T e : targetType.getEnumConstants()) {
            if (e.getCode().equals(source)) {
                return e;
            }
        }

        throw new IllegalArgumentException("枚举 Code 不正确");
    }
}

3

总结

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

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

- <End /> -


历史文章 | 相关推荐

45669c0f3aae9fb27bcb91a047752242.gif

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Spring Boot转换器是一种用于将输入数据从一种类型转换为另一种类型的机制。转换器用于将HTTP请求中的请求参数转换为控制器方法参数的类型,并将控制器方法的返回值转换为HTTP响应的类型。 Spring Boot提供了许多内置的转换器,例如: - StringToIntegerConverter:将字符串转换为整数。 - StringToBooleanConverter:将字符串转换为布尔值。 - StringToEnumConverter:将字符串转换为枚类型。 - StringToLocalDateConverter:将字符串转换为本地日期。 - StringToLocalDateTimeConverter:将字符串转换为本地日期时间。 除了内置转换器之外,Spring Boot还允许开发人员创建自定义转换器。自定义转换器应实现Converter接口,该接口定义了两个方法:convert()和getType()。convert()方法将输入数据转换为目标类型,getType()方法返回目标类型的Class对象。 要注册自定义转换器,可以使用ConversionService或FormatterRegistry。ConversionService是一个通用的类型转换服务,FormatterRegistry用于注册格式化程序和转换器。可以使用@Configuration类中的addFormatters()方法将转换器添加到FormatterRegistry中。例如: ``` @Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new MyConverter()); } } ``` 上述代码将MyConverter添加到FormatterRegistry中。现在,Spring Boot应用程序将使用MyConverter将输入数据从一种类型转换为另一种类型。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小航

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值