深入解析Spring使用枚举接收参数和返回值机制并提供自定义最佳实践

2023-10-29

Spring对应枚举传参/返回值默认是用字面量实现的(实际情况更复杂),而《阿里巴巴Java开发手册》规定接口返回值不可以使用枚举类型(包括含枚举类型的POJO对象),为此,本文探究了Spring内部对枚举参数的传递和处理机制,并提供了一套自定义方案。

一 目标与思路

0 起因

《阿里巴巴Java开发手册》将接口中枚举的使用分为两类,即 接口参数和接口返回值,并规定:
接口参数可以使用枚举类型,但接口返回值不可以使用枚举类型(包括含枚举类型的POJO对象)

知乎有相关讨论和作者亲答,详情可见:Java枚举什么不好,《阿里巴巴JAVA开发手册》对于枚举规定的考量是什么?

现摘录一部分作者回答如下:

由于升级原因,导致双方的枚举类不尽相同,在接口解析,类反序列化时出现异常

Java中出现的任何元素,在Gosling的角度都会有背后的思考和逻辑(尽管并非绝对完美,但Java的顶层抽象已经是天才级了),比如:接口、抽象类、注解、和本文提到的枚举。枚举有好处,类型安全,清晰直接,还可以使用等号来判断,也可以用在switch中。它的劣势也是明显的,就是不要扩展。可是为什么在返回值和参数进行了区分呢,如果不兼容,那么两个都有问题,怎么允许参数可以有枚举。当时的考虑,如果参数也不能用,那么枚举几乎无用武之地了。参数输出,毕竟是本地决定的,你本地有的,传送过去,向前兼容是不会有问题的。但如果是接口返回,就比较恶心了,因为解析回来的这个枚举值,可能本地还没有,这时就会抛出序列化异常。

比如:你的本地枚举类,有一个天气Enum:SUNNY, RAINY, CLOUDY,如果根据天气计算心情的方法:guess(WeatcherEnum xx),传入这三个值都是可以的。返回值:Weather guess(参数),那么对方运算后,返回一个SNOWY,本地枚举里没有这个值,傻眼了。

当然,使用 code 照样不能处理,对此,开发手册作者的回答如下

主要是从防止这种序列化异常角度来考虑,使用code至少不会出大乱子。而catch序列化异常,有点像catch(NullPointerException e)一样代码过度,因为它是可预检异常。

1 统一称谓

假如有一枚举类如下:

public enum ReturnCodeEnum {
   
    OK(200),
    ERROR(500)
    ;
    private final int code;
    ReturnCodeEnum(int code){
   
        this.code=code;
    }
    public int getCode() {
   
        return code;
    }
}

枚举实例有两个默认属性,nameordinal,可通过 name()和ordinal()方法分别获得。其中 name 为枚举字面量(如 OK),ordinal 为枚举实例默认次序(从0开始)
需要注意的是,不建议使用枚举的 ordinal,因为枚举实例应该是无序的,ordinal 提供的顺序是不可靠的,所以我们应该使用自定义的枚举字段 code。

后文为方便阐述,以 字面量(name)、默认次序(ordinal)和 code来展开阐述。如 OK 的 字面量为 OK,ordinal 为 0 ,code为 200。

2 目标

目标
  1. 直接使用 枚举类型 接收参数返回值
  2. 系统自动将 参数中的 code 转换为 枚举类型,自动将 返回值中的枚举类型转换为 code
实现效果

对于实现通用code枚举接口的枚举类型,有如下效果:

  1. 使用 bean(application/x-www-form-urlencoded)接收时,支持 code 自动转换为 枚举类型,同时兼容 字面量转换为枚举类型。注意:表单接收的参数都视为 String,即是将String转为 枚举类型
  2. 使用 @RequestBody (application/json)接收时,默认只支持 code 自动转换为枚举类型。如果需要同时支持 code 和 字面量(或者只支持字面量),可以在具体的枚举类里添加@JsonCreator注解的方法,下文会给出参考实现。
  3. 可以使用 @RequestParam 和 @PathVariable 接收枚举类型参数
  4. 使用 @ResponseBody / @RestController(返回 Json)时,默认将 枚举类型转换为 code。
  5. 在接收参数/返回值都不允许使用 ordinal ,这只会导致混乱。

3 SpringMVC 对 枚举参数的处理

此处只对 restful 接口进行讨论。对于 restful 接口,Spring MVC 的返回值是使用 @ResponseBody 进行处理的。
而参数的接收方式则较多,对于非简单类型,如 Enum ,一般的接收方法为 Bean 接收或 @ResponseBody 接收。

Spring使用Bean接收枚举参数

简单来说 Spring 默认使用Bean接收枚举参数时支持 字面量,这也是我们常见的做法。

参考自:Spring与枚举参数

GET 请求和 POST Form 请求中的字符串到枚举的转化是通过 org.springframework.core.convert.support.StringToEnumConverterFactory 来实现的.
该类实现了接口 ConverterFactory ,通过调用 Enum.valueOf(Class, String) 实现了这个功能。
向下追溯源码可以发现该方法实际上是从一个 Map<String, Enum> 的字典中获取了转换后的实际值,着这个 String 类型的 Key 的获取方式就是 Enum.name() 返回的结果,即枚举的字面值

Spring使用@RequestBody 接收枚举参数

简单来说 Spring使用@RequeseBody 接收枚举参数时支持 字面量和 ordinal

对于@RequestBody,Spring会将其内容视为一段 Json,所做工作为使用 Jackson 完成反序列化。其实现会经过Jackson的EnumDeserializer的deserialize方法。感兴趣的可以去看看源码,这里不贴出来,讲一下思路:

  1. 使用字面量(String)进行反序列化
  2. 判断是否是 int 类型,如果是使用 ordinal 进行反序列化,如果数字不在 ordinal 里面,则抛异常
  3. 判断是否是数组,是的话交由数组处理,否则抛异常
Spring使用@ResponseBody 返回值

如我们平常使用所见,返回的是字面量

4 思路

参照Spring对枚举参数的处理,我们可以提供覆盖/替换Spring的处理来达到我们的效果,
经本人测试,比较好的实现方案有(不考虑反射):

  1. 自定义Bean接收枚举参数规则:
    1. 可行方案
      通过Spring MVC注入特定类型自定义转换器实现从code到 枚举的自动转换
    2. 做法
      使用 WebMvcConfigurer的addFormatters注入自定义ConverterFactory,该工厂负责生成 通用code枚举接口的实现类对应的转换器
      详见第二部分–代码实现。
    3. 参考资料
      Spring Boot绑定枚举类型参数
  2. 自定义@RequestBody 和@ResponseBody处理枚举参数
    1. 可行方案
      使用@JsonValue自定义特定枚举类的Jackson序列化/反序列化方式

      1. 具体做法
        使用 @JsonValue注解标记 获取code值的枚举实例方法。
      2. 注意事项
        该code值是使用jackson序列化/反序列化时枚举对应的值,会覆盖原来从字面量反序列化回枚举的默认实现。
        如果想要保留原来从字面量反序列化回枚举类的功能,需要自定义一个@JsonCreator 的构造/静态工厂方法。
      3. 相关代码
        代码如下:
        @JsonValue
        public int getCode() {
                 
            return code;
        }
        
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入解析Spring使用枚举接收参数和返回值机制并提供自定义最佳实践 的相关文章

随机推荐

  • 3D Web轻量化引擎HOOPS:轻松解决OSGB模型复杂性与性能挑战!

    在当今的数字时代 三维模型的创建和展示对于众多行业都至关重要 无论是用于游戏开发 虚拟现实体验 建筑设计还是工程仿真 高质量的3D模型都能够提供更真实的视觉效果和更精确的数据表示 然而 随着模型的复杂性增加 其数据量也迅速膨胀 这可能会导致
  • Kafka的生成者、消费者、broker的基本概念

    kafka是一款基于发布与订阅的消息系统 它一般被称为 分布式提交日志 或者 分布式流平台 文件系统或者数据库提交日志用来提供所有事物的持久化记录 通过重建这些日志可以重建系统的状态 同样地 kafka的数据是按照一定顺序持久化保存的 可以
  • sql总结喔

    一 数据库基础 如何开服务 2个 TCP IP协议 SQL Server MSSQLSERVER 系统数据库 master 核心 模板 model 模型 msdb 警告日志 tempdb 临时 数据库文件分为数据文件 mdf ndf 和日志
  • SPI通信原理和协议

    1 SPI原理超详细讲解 值得一看 https blog csdn net as480133937 article details 105764119 2 STM32 HAL库 STM32CubeMX教程十四 SPI https blog
  • 2021-11-11密码学

    对称密码体系中发送者和接收者使用同一个密钥加密和解密 对称 可逆 非对称密码体系中发送者使用接收者的公钥进行加密 接收者使用自己的私钥进行解密 非对称 五种对称加密算法总结 1 DES 已破解 不再安全 基本没有企业在用了 是对称加密算法的
  • idea 配置文件乱码修改

    Settings gt Editor gt File Encodings 设置统一编码
  • As 启动项目出现unsupported modules detected

    首先是出现如下情况 unsupported modules detected Compilation is not supported for following modules app Unfortunately you can t ha
  • python yield关键字全面解析

    你是否曾因处理的数据集过大而内存溢出 你是否曾因为处理各种复杂的函数状态而烦恼 It does help 本文聚焦yield generator 帮助你解锁python进阶技法 写出更优雅的程序 先导概念 为了更好的理解本篇推文的内容 读者
  • 计算机网络笔记七(数据链路层:帧、MTU、ARP协议)

    1 数据链路层 1 1主要功能 用于两个设备 同一种数据链路节点 之间进行信息传递 网络层和数据链路层对比 网络层是进行地址管理和路由选择的 它是为数据报的转发找出一条路来 而数据链路层解决的是两个结点直接的数据交换 接近于物理层的概念 1
  • 线性代数学习笔记——矩阵(二)(针对期末与考研)

    伴随矩阵 设 A a i j A a ij
  • wazuh初探系列一 : wazuh环境配置

    目录 方法一 一体化部署 安装先决条件 第一步 安装所有必需的软件包 第二步 安装Elasticsearch 1 添加 Elastic Stack 存储库 安装 GPG 密钥 添加存储库 更新源 2 Elasticsearch安装和配置 安
  • 建表时出现10654错误the right syntax to use near‘NUL COMMENT‘

    创建表时 出现 10654 You have an err in your SQl sytaxy check the manual that corresponds to your MySQL sever version for the r
  • 从零开始搭建创业公司后台技术栈

    转自 http ju outofmemory cn entry 351897 编辑 公众号程序员面试 前言 说到后台技术栈 脑海中是不是浮现的是这样一幅图 图 1 有点眼晕 以下只是我们会用到的一些语言的合集 而且只是语言层面的一部分 就整
  • 服务器虚拟化方需求分析报告,服务器虚拟化解决方案报告书.doc

    WORD格式可编辑 专业知识整理分享 服务器虚拟化解决方案 Citrix XenServer服务器虚拟化解决方案 1 1 方案综述 1 1 1服务器虚拟化的业务及应用需求 随着企业业务的飞速发展 越来越多的业务系统依赖于数据中心的支撑 其中
  • 泛微移动表单校验手机号跟邮箱

    泛微移动表单校验手机号跟邮箱 var iphoneVal f phone val 获取表单字段 var checkIphone d 1 345789 d
  • h5上下滑动动画效果(vue)

    1 详情介绍 图片可以使用网络图片 根据请求过来的图片来获取高度要控制滑动的位置 可以换成视频 要实现滑动播放视频的效果 并且可以在上面添加一些其他的功能 白色背景区域可以展示对应的数据 具体效果看文章末尾 2 编码介绍 template部
  • UNIX环境高级编程 学习笔记 第十九章 伪终端

    终端登录是经由终端设备进行的 终端设备天然提供终端语义 在终端和运行程序之间有一个终端行规程 通过该规程我们能设置终端特殊字符 如退格 行删除 中断等 但当一个登录请求到达网络连接时 终端行规程并不是自动被加载到网络连接和登录shell之间
  • VSCode在linux服务器下launch.json和tasks.json等文件配置

    前言 用win下的VSCode远程连接linux服务器后 就可以用VSCode运行linux下代码了 在编译运行代码前 按照VSCode的要求 我们要给编译和执行操作分别配置一堆参数 这就是launch json和tasks json配置的
  • Android WebView加载优化,Android WebView 优化页面加载效果

    目前带有Web功能的APP越来越多 为了能够更好的使用WebView展示页面 可以考虑做相关的优化 WebView 缓存 资源文件本地存储 客户端UI优化 目前webapp越来越多 体验也越来越好 为了能够更好的使用WebView展示出流畅
  • 深入解析Spring使用枚举接收参数和返回值机制并提供自定义最佳实践

    Spring对应枚举传参 返回值默认是用字面量实现的 实际情况更复杂 而 阿里巴巴Java开发手册 规定接口返回值不可以使用枚举类型 包括含枚举类型的POJO对象 为此 本文探究了Spring内部对枚举参数的传递和处理机制 并提供了一套自定