当前位置: 首页 > news >正文

不用 if-else,Spring Boot 怎么知道 ?status=10 是哪个枚举?

图片

几乎在每个项目中,我们都会定义大量的枚举(Enum)来表示状态、类型等。一个常见的实践是为枚举赋予一个数值或字符串 code,以便在数据库和前后端交互中使用,例如:

public enum OrderStatusEnum {PENDING_PAYMENT(10, "待支付"),PROCESSING(20, "处理中"),SHIPPED(30, "已发货");private final int code;private final String description;// ...
}

但问题来了:当后端 Controller 接收前端传来的参数(如 ?status=10)时,Spring MVC 默认并不知道如何将 10 这个 Integer 自动转换为 OrderStatusEnum.PENDING_PAYMENT。于是,我们的 Controller 代码常常会变成这样:

@GetMapping("/orders")
public List<Order> getOrders(@RequestParam Integer status) {OrderStatusEnum statusEnum = OrderStatusEnum.fromCode(status); // 手动转换if (statusEnum == null) {throw new IllegalArgumentException("Invalid status code");}// ...
}

这种手动转换的代码充满了 if-else 和重复的校验逻辑,非常丑陋。本文将带你构建一个通用的枚举转换 Starter,让你的 Controller 可以直接、优雅地接收枚举类型,彻底告别这些样板代码。

1. 项目设计与核心思路

我们的 enum-converter-starter 目标如下:

  1. 1. 通用性: 无需为每个枚举都写一个转换器,一个 Starter 解决所有问题。

  2. 2. 约定驱动: 只要枚举遵循一个简单的约定(实现一个通用接口),就能被自动识别和转换。

  3. 3. 自动注册: 引入 Starter 依赖后,转换逻辑自动在 Spring MVC 中生效。

核心实现机制:ConverterFactory
Spring 框架提供了一个 ConverterFactory<S, R> 接口。它是一个能创建 Converter<S, T extends R> 实例的工厂。我们可以创建一个 ConverterFactory<String, Enum>,它能为任何 Enum 类型的子类 T 创建一个从 String 到 T 的转换器。

实现流程:

  1. 1. 定义一个通用接口,如 BaseEnum,它包含一个 getCode() 方法。

  2. 2. 所有需要被自动转换的枚举都实现 BaseEnum 接口。

  3. 3. 创建一个 StringToEnumConverterFactory,它会为所有实现了 BaseEnum 接口的枚举,生成一个能根据 getCode() 的值进行匹配的转换器。

  4. 4. 通过 WebMvcConfigurer 将这个 ConverterFactory 注册到 Spring 的格式化服务中。

2. 创建 Starter 项目与核心组件

我们采用 autoconfigure + starter 的双模块结构。

步骤 2.1: 依赖 (autoconfigure 模块)

这个 Starter 非常轻量,核心依赖只需要 spring-boot-starter-web

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
步骤 2.2: 定义约定接口和通用转换工厂

BaseEnum (约定接口):

package com.example.converter.autoconfigure.core;public interface BaseEnum {/*** 获取枚举的代码值* @return code 值 (可以是 Integer, String 等)*/Object getCode();
}

StringToEnumConverterFactory (核心转换逻辑):

package com.example.converter.autoconfigure.core;import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;public class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {@Overridepublic <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {// 我们只处理实现了 BaseEnum 接口的枚举if (!BaseEnum.class.isAssignableFrom(targetType)) {// 对于未实现接口的枚举,使用 Spring 默认的转换器 (按名称匹配)return new StringToEnumConverter(targetType);}return new StringToBaseEnumConverter<>(targetType);}// 内部类,负责将 String 转换为实现了 BaseEnum 的枚举private static class StringToBaseEnumConverter<T extends Enum<?>> implements Converter<String, T> {private final Class<T> enumType;StringToBaseEnumConverter(Class<T> enumType) {this.enumType = enumType;}@Overridepublic T convert(String source) {if (source.isEmpty()) {return null;}for (T enumConstant : enumType.getEnumConstants()) {if (enumConstant instanceof BaseEnum) {// 使用 getCode() 的值进行比较if (String.valueOf(((BaseEnum) enumConstant).getCode()).equals(source)) {return enumConstant;}}}return null; // or throw exception}}// 内部类,用于兼容 Spring 默认的按名称转换private static class StringToEnumConverter<T extends Enum> implements Converter<String, T> {private final Class<T> enumType;public StringToEnumConverter(Class<T> enumType) {this.enumType = enumType;}@Overridepublic T convert(String source) {if (source.isEmpty()) {return null;}return (T) Enum.valueOf(this.enumType, source.trim());}}
}

3. 自动装配的魔法 (EnumConverterAutoConfiguration)

步骤 3.1: 配置属性类
@ConfigurationProperties(prefix = "enum.converter")
public class EnumConverterProperties {private boolean enabled = true; // 默认开启// Getters and Setters...
}
步骤 3.2: 自动配置主类

这个类负责将我们的 ConverterFactory 注册到 Spring MVC。

package com.example.converter.autoconfigure;import com.example.converter.autoconfigure.core.StringToEnumConverterFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
@EnableConfigurationProperties(EnumConverterProperties.class)
@ConditionalOnProperty(prefix = "enum.converter", name = "enabled", havingValue = "true", matchIfMissing = true)
public class EnumConverterAutoConfiguration implements WebMvcConfigurer {@Overridepublic void addFormatters(FormatterRegistry registry) {// 将我们的通用转换工厂注册进去registry.addConverterFactory(new StringToEnumConverterFactory());}
}
步骤 3.3: 注册自动配置

在 autoconfigure 模块的 resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中添加:

com.example.converter.autoconfigure.EnumConverterAutoConfiguration

4. 如何使用我们的 Starter

步骤 4.1: 引入 Starter 依赖

<dependency><groupId>com.example</groupId><artifactId>enum-converter-spring-boot-starter</artifactId><version>1.0.0</version>
</dependency>

步骤 4.2: 让你的枚举实现约定接口

import com.example.converter.autoconfigure.core.BaseEnum;public enum OrderStatusEnum implements BaseEnum {PENDING_PAYMENT(10, "待支付"),PROCESSING(20, "处理中"),SHIPPED(30, "已发货");private final Integer code;private final String description;OrderStatusEnum(Integer code, String description) {this.code = code;this.description = description;}@Overridepublic Integer getCode() {return this.code;}
}

步骤 4.3: 在 Controller 中直接接收枚举类型
现在,你的 Controller 可以写得无比清爽:

改造前 (丑陋):

// @GetMapping("/orders")
// public List<Order> getOrdersByStatusCode(@RequestParam Integer status) {
//     OrderStatusEnum statusEnum = // ... 手动 if-else 或 switch 转换
//     return orderService.findByStatus(statusEnum);
// }

改造后 (优雅):

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class OrderController {@GetMapping("/orders")public String getOrdersByStatus(@RequestParam OrderStatusEnum status) {// Spring MVC 已经自动将请求参数 "10" 转换为了 OrderStatusEnum.PENDING_PAYMENTSystem.out.println("查询状态为: " + status.name());return "查询成功,状态为: " + status;}
}

验证:

  • • 访问 http://localhost:8080/orders?status=20

  • • 控制台将打印 查询状态为: PROCESSING

  • • 浏览器将收到 查询成功,状态为: PROCESSING

总结

通过自定义一个 Spring Boot Starter 和巧妙地利用 ConverterFactory,我们将繁琐、重复的枚举转换逻辑从业务代码中彻底剥离。这不仅让 Controller 层代码变得更加简洁、类型安全,还通过一个统一的 BaseEnum 接口,在团队内部推行了一套优雅的枚举设计规范。

这个看似小巧的 Starter,是提升代码质量和“开发幸福感”的一大利器,是每一个追求代码洁癖的团队都值得拥有的基础组件。

http://www.dtcms.com/a/346922.html

相关文章:

  • 全面解析JVM预热:原理、价值与实践指南
  • Mybatis Plus - 代码生成器简单使用
  • SSE实时通信与前端联调实战
  • 内网穿透教程
  • 亚马逊布局墨西哥低价赛道:Amazon Bazaar的战略逻辑与卖家破局路径
  • STM32CubeIDE V1.9.0下载资源链接
  • 水体反光 + 遮挡难题破解!陌讯多模态融合算法在智慧水务的实测优化
  • RAG学习(六)——检索优化技术进阶
  • Sqlserver存储过程
  • 拼豆设计生成器(支持大写字母、数字,颜色自定义)
  • 力扣 30 天 JavaScript 挑战 第38天 (第九题)学习了 语句表达式的区别 高级函数 promise async await 节流
  • 三、Bpmnjs 核心组件与架构介绍
  • 深入剖析结构体内存对齐
  • 达梦数据库巡检常用SQL(一)
  • Base64 编码优化 Web 图片加载:异步响应式架构(Java 后端 + 前端全流程实现)
  • Linux问答题:分析和存储日志
  • [特殊字符] 在 Windows 新电脑上配置 GitHub SSH 的完整记录(含坑点与解决方案)
  • JUC之AQS
  • csrf漏洞学习笔记
  • C++ 20: Concepts 与Requires
  • 告别SaaS数据绑架,拥抱数据主权:XK+独立部署版跨境商城定制,为海外物流企业深度赋能
  • CentOS创建管理员用户feixue并设置密码全教程
  • 【c++进阶系列】:万字详解多态
  • 快速掌握Java非线性数据结构:树(二叉树、平衡二叉树、多路平衡树)、堆、图【算法必备】
  • STM32学习笔记19-WDG
  • linux shell测试函数
  • 百度深度学习面试:batch_size的选择问题
  • Linux总线设备驱动模型深度理解
  • 玩转Vue3高级特性:Teleport、Suspense与自定义渲染
  • 内联函数是什么以及的优点和缺点