关键词:
该图片由Christian_Crowd在Pixabay上发布
你好,我是看山。
前文说到 优雅的使用枚举参数 和 实现原理,本文继续说一下如何在 RequestBody 中优雅使用枚举。
本文先上实战,说一下如何实现。在 优雅的使用枚举参数 代码的基础上,我们继续实现。如果想要获取源码,可以关注公号「看山的小屋」,回复 spring 即可。
确认需求
需求与前文类似,只不过这里需要是在 RequestBody 中使用。与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 json 或 xml 格式,Spring 默认借助 Jackson 反序列化为对象。
同样的,我们需要在枚举中定义 int 类型的 id、String 类型的 code,id 取值不限于序号(即从 0 开始的 orinal 数据),code 不限于 name。客户端请求过程中,可以传 id,可以传 code,也可以传 name。服务端只需要在对象中定义一个枚举参数,不需要额外的转换,即可得到枚举值。
好了,接下来我们定义一下枚举对象。
定义枚举和对象
先定义我们的枚举类GenderIdCodeEnum
,包含 id 和 code 两个属性:
public enum GenderIdCodeEnum implements IdCodeBaseEnum
MALE(1, "male"),
FEMALE(2, "female");
private final Integer id;
private final String code;
GenderIdCodeEnum(Integer id, String code)
this.id = id;
this.code = code;
@Override
public String getCode()
return code;
@Override
public Integer getId()
return id;
这个枚举类的要求与前文一致,不清楚的可以再去看一下。
在定义一个包装类GenderIdCodeRequestBody
,用于接收 json 数据的请求体:
@Data
public class GenderIdCodeRequestBody
private String name;
private GenderIdCodeEnum gender;
private long timestamp;
除了GenderIdCodeEnum
参数外,其他都是示例,所以随便定义一下。
实现转换逻辑
前奏铺垫好,接下来入正题了。Jackson 提供了两种方案:
- 方案一:精准攻击,指定需要转换的字段,不影响其他类对象中的字段
- 方案二:全范围攻击,所有借助 Jackson 反序列化的枚举字段,全部具备自动转换功能
方案一:精准攻击
这种方案中,我们首先需要实现JsonDeserialize
抽象类:
public class IdCodeToEnumDeserializer extends JsonDeserializer<BaseEnum>
@Override
public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException
final String param = jsonParser.getText();// 1
final JsonStreamContext parsingContext = jsonParser.getParsingContext();// 2
final String currentName = parsingContext.getCurrentName();// 3
final Object currentValue = parsingContext.getCurrentValue();// 4
try
final Field declaredField = currentValue.getClass().getDeclaredField(currentName);// 5
final Class<?> targetType = declaredField.getType();// 6
final Method createMethod = targetType.getDeclaredMethod("create", Object.class);// 7
return (BaseEnum) createMethod.invoke(null, param);// 8
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e)
throw new CodeBaseException(ErrorResponseEnum.PARAMS_ENUM_NOT_MATCH, new Object[] param, "", e);
然后在指定枚举字段上定义@JsonDeserialize
注解,比如:
@JsonDeserialize(using = IdCodeToEnumDeserializer.class)
private GenderIdCodeEnum gender;
具体说一下每行的作用:
- 获取参数值。根据需要,此处可能是 id、code 或 name,也就是源值,需要将其转换为枚举;
- 获取转换上线文,这个是为 3、4 步做准备的;
- 获取标记
@JsonDeserialize
注解的字段,此时currentName
的值是gender
; - 获取包装对象,也就是
GenderIdCodeRequestBody
对象; - 根据包装对象的
Class
对象,以及字段名gender
获取Field
对象,为第 5 步做准备; - 获取
gender
字段对应的枚举类型,也即是GenderIdCodeEnum
。之所以这样做,是要实现一个通用的反序列化类; - 这里是写死的一种实现,就是在枚举类中,需要定义一个静态方法,方法名是
create
,请求参数是Object
; - 通过反射调用
create
方法,将第一步获取的请求参数传入。
我们来看一下枚举类中定义的create
方法:
public static GenderIdCodeEnum create(Object code)
final String stringCode = code.toString();
final Integer intCode = BaseEnum.adapter(stringCode);
for (GenderIdCodeEnum item : values())
if (Objects.equals(stringCode, item.name()))
return item;
if (Objects.equals(item.getCode(), stringCode))
return item;
if (Objects.equals(item.getId(), intCode))
return item;
return null;
为了性能考虑,我们可以提前定义三组 map,分别以 id、code、name 为 key,以枚举值为 value,这样就可以通过 O(1) 的时间复杂度返回了。可以参考前文的Converter
类的实现逻辑。
这样,我们就可以实现精准转换了。
方案二:全范围攻击
这种方案是全范围攻击了,只要是 Jackson 参与的反序列化,只要其中有目标枚举参数,就会受到这种进入这种方案的逻辑中。这种方案是在枚举类中定义一个静态转换方法,通过@JsonCreator
注解注释,Jackson 就会自动转换了。
这个方法的定义与方案一中的create
方法完全一致,所以只需要在create
方法上加上注解即可:
@JsonCreator(mode = Mode.DELEGATING)
public static GenderIdCodeEnum create(Object code)
final String stringCode = code.toString();
final Integer intCode = BaseEnum.adapter(stringCode);
for (GenderIdCodeEnum item : values())
if (Objects.equals(stringCode, item.name()))
return item;
if (Objects.equals(item.getCode(), stringCode))
return item;
if (Objects.equals(item.getId(), intCode))
return item;
return null;
其中Mode
类有四个值:DEFAULT
、DELEGATING
、PROPERTIES
、DISABLED
,这四种的差别会在原理篇中说明。还是那句话,对于应用类技术,我们可以先知其然,再知其所以然,也一定要知其所以然。
测试
先定义一个 controller 方法:
@PostMapping("gender-id-code-request-body")
public GenderIdCodeRequestBody bodyGenderIdCode(@RequestBody GenderIdCodeRequestBody genderRequest)
genderRequest.setTimestamp(System.currentTimeMillis());
return genderRequest;
然后定义测试用例,还是借助 JUnit5:
@ParameterizedTest
@ValueSource(strings = "\\"MALE\\"", "\\"male\\"", "\\"1\\"", "1")
void postGenderIdCode(String gender) throws Exception
final String result = mockMvc.perform(
MockMvcRequestBuilders.post("/echo/gender-id-code-request-body")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content("\\"gender\\": " + gender + ", \\"name\\": \\"看山\\"")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse()
.getContentAsString();
ObjectMapper objectMapper = new ObjectMapper();
final GenderIdCodeRequestBody genderRequest = objectMapper.readValue(result, GenderIdCodeRequestBody.class);
Assertions.assertEquals(GenderIdCodeEnum.MALE, genderRequest.getGender());
Assertions.assertEquals("看山", genderRequest.getName());
Assertions.assertTrue(genderRequest.getTimestamp() > 0);
文末总结
本文主要说明了如何在 RequestBody 中优雅的使用枚举参数,借助了 Jackson 的反序列化扩展,可以定制类型转换逻辑。碍于文章篇幅,没有罗列大段代码。关注公号「看山的小屋」回复 spring 可以获取源码。关注我,下一篇我们进入原理篇。
推荐阅读
- SpringBoot 实战:一招实现结果的优雅响应
- SpringBoot 实战:如何优雅的处理异常
- SpringBoot 实战:通过 BeanPostProcessor 动态注入 ID 生成器
- SpringBoot 实战:自定义 Filter 优雅获取请求参数和响应结果
- SpringBoot 实战:优雅的使用枚举参数
- SpringBoot 实战:优雅的使用枚举参数(原理篇)
- SpringBoot 实战:在 RequestBody 中优雅的使用枚举参数
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。
个人主页:https://www.howardliu.cn
个人博文:SpringBoot 实战:在 RequestBody 中优雅的使用枚举参数
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:SpringBoot 实战:在 RequestBody 中优雅的使用枚举参数
springboot@requestbody注解学习(代码片段)
文章引用@RequestBody的使用功能介绍@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而... 查看详情
springboot中@requestbody和@reponsebody获取参数
一 @RequestBody和@ReponseBody二者的作用与联系1.1说明@RequestBody 需要使用post的提交方式,需要传递的数据格式为json格式,它的作用就是把json数据封装到对象里面@ReuestBody(required=false Usersuser) 参数可以为空@... 查看详情
springboot的函数参数(javabean对象@requestparam与@requestbody)
Spring Boot + Swagger + Swagger UI 和 @RequestBody 具有数据类型 String
】SpringBoot+Swagger+SwaggerUI和@RequestBody具有数据类型String【英文标题】:SpringBoot+Swagger+SwaggerUIand@RequestBodyhasdatatypeString【发布时间】:2016-12-1102:14:17【问题描述】:我在使用SpringBoot1.4和Swagger和SwaggerUI时遇到问题。使用@RequestBody参数... 查看详情
将 RequestBody json 转换为对象 - Spring Boot
】将RequestBodyjson转换为对象-SpringBoot【英文标题】:ConvertingaRequestBodyjsontoanObject-SpringBoot【发布时间】:2017-08-2321:41:20【问题描述】:我是Java开发的初学者,但之前有PHP和Python等编程语言的经验。对于如何在SpringBoot上进行开发,... 查看详情
如何在@ExceptionHandler(Spring REST)中获取@RequestBody
】如何在@ExceptionHandler(SpringREST)中获取@RequestBody【英文标题】:Howtogetthe@RequestBodyinan@ExceptionHandler(SpringREST)【发布时间】:2017-09-1601:45:01【问题描述】:我正在使用SpringBoot1.4.1,其中包括spring-web-4.3.3。我有一个用@ControllerAdvice... 查看详情
Spring Boot - 验证@RequestBody
】SpringBoot-验证@RequestBody【英文标题】:Springboot-validate@RequestBody【发布时间】:2021-02-0713:17:27【问题描述】:问题:是否可以验证请求正文的JSON有效负载,而无需专门编写if语句?也许通过注释或配置?我有一个非常简单的POJO... 查看详情
控制器方法中 requestBody 上的 Spring Boot @Valid 不起作用
】控制器方法中requestBody上的SpringBoot@Valid不起作用【英文标题】:Springboot@ValidonrequestBodyincontrollermethodnotworking【发布时间】:2020-09-1112:17:27【问题描述】:我正在尝试在由@Validated注释的@RestController中验证由@Valid注释注释的简单... 查看详情
解决springboot中@requestbody不能和multipart同时传递的问题
问题描述今天在做文件上传的时候,遇到了这么一个错误日志:Resolved[org.springframework.web.HttpMediaTypeNotSupportedException:Contenttype‘multipart/form-data;boundary=--------------------------771899451541318130280588;charset=UTF-8’notsupported]分析... 查看详情
Swagger (Springfox) 仅查找 Controller @RequestBody (Spring Boot) 中使用的模型
】Swagger(Springfox)仅查找Controller@RequestBody(SpringBoot)中使用的模型【英文标题】:Swagger(Springfox)onlyfindingModelsusedinController@RequestBody(SpringBoot)【发布时间】:2020-04-1120:05:13【问题描述】:基本上,我感兴趣的是,Swagger在swagger-ui中显示... 查看详情
原创003|搭上基于springboot事务思想实战专车
...意思,就是需要你的窥屏^_^专车介绍该趟专车是开往基于SpringBoot事务思想实战的专车,在上一篇搭上SpringBoot事务源码分析专车[1]中我们详细介绍了SpringBoot事务实现的原理,这一篇是基于上一篇的实战。在实战之前,我们再次回... 查看详情
关于@requestbody的一些分析
参考技术A最近在看基于SpringBoot的登录逻辑,项目测试时使用Postman模拟表单登录请求,发现,程序总是报415,也就是不支持的类型。网上查了一圈,猜测可能是前后端参数不匹配导致的问题。排查后发现@RequestBody有个坑:无法接... 查看详情
springboot实战
SpringBoot精要 SpringBoot的四个核心 1.自动配置:针对很多Spring应用程序常见的应用功能,SpringBoot能自动提供相关配置。 在任何Spring应用程序的源代码中,都可以找到java配置或XML配置,他们为应用程序开... 查看详情
springboot使用@requestbody报错4002019-10-26
参考技术A大意就是请求被CORS策略阻止。2019/10/27找到了原因: vue 发送json,会导致此次请求被认为是一次复杂的请求,所以会发送两次请求(为什么会发送两次请自行查询),第一次不带任何数据,服务端口的@Requese... 查看详情
springboot参数校验,高级特性,非常实用
参考技术A之前也写过一篇关于SpringValidation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂SpringValidation。本文会详细介绍SpringValidation各种场景下的最佳实践及其实现原理,死磕到底!JavaAPI规范(JSR303)定义了Bean校验... 查看详情
spring-boot实战09:springboot中使用@scheduled创建定时任务
我们在编写SpringBoot应用中经常会遇到这样的场景,比如:我需要定时地发送一些短信、邮件之类的操作,也可能会定时地检查和监控一些标志、参数等。创建定时任务在SpringBoot中编写定时任务是非常简单的事,下面通过实例介... 查看详情
springboot实战(第一篇)第一个案例
...主原创文章,未经博主允许不得转载。 目录(?)[+] springboot实战(第一篇)第一个案例前言写在前面的话一直想将springboot相关内容写成一个系列的博客,今天终于有时间开始了第一篇文章以后有时间就会继续写下去。springboot... 查看详情
springboot2从入门到入坟|请求参数处理篇:常用参数注解之@requestbody(代码片段)
在本讲,我会再来为大家介绍一个常用的参数注解,即@RequestBody,关于该注解,只要是学过一点SpringMVC的人,都会对它无比的熟悉,因为它无非就是用于获取请求体的。既然是获取请求体,那么必然是来获取post方式的请求的请... 查看详情