SpringBoot在RequestBody中使用枚举参数案例详解

前文说到 优雅的使用枚举参数 和 实现原理,本文继续说一下如何在 RequestBody 中优雅使用枚举。

本文先上实战,说一下如何实现。在 优雅的使用枚举参数 代码的基础上,我们继续实现。

确认需求

需求与前文类似,只不过这里需要是在 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;

具体说一下每行的作用:

  1. 获取参数值。根据需要,此处可能是 id、code 或 name,也就是源值,需要将其转换为枚举;
  2. 获取转换上线文,这个是为 3、4 步做准备的;
  3. 获取标记@JsonDeserialize注解的字段,此时currentName的值是gender
  4. 获取包装对象,也就是GenderIdCodeRequestBody对象;
  5. 根据包装对象的Class对象,以及字段名gender获取Field对象,为第 5 步做准备;
  6. 获取gender字段对应的枚举类型,也即是GenderIdCodeEnum。之所以这样做,是要实现一个通用的反序列化类;
  7. 这里是写死的一种实现,就是在枚举类中,需要定义一个静态方法,方法名是create,请求参数是Object
  8. 通过反射调用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类有四个值:DEFAULTDELEGATINGPROPERTIESDISABLED,这四种的差别会在原理篇中说明。还是那句话,对于应用类技术,我们可以先知其然,再知其所以然,也一定要知其所以然。

测试

先定义一个 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 中优雅的使用枚举参数
  • SpringBoot 实战:在 RequestBody 中优雅的使用枚举参数(原理篇)

到此这篇关于SpringBoot在RequestBody中使用枚举参数案例详解的文章就介绍到这了,更多相关SpringBoot在RequestBody中使用枚举参数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot如何使用RequestBodyAdvice进行统一参数处理

    SpringBoot RequestBodyAdvice参数处理 在实际项目中 , 往往需要对请求参数做一些统一的操作 , 例如参数的过滤 , 字符的编码 , 第三方的解密等等 , Spring提供了RequestBodyAdvice一个全局的解决方案 , 免去了我们在Controller处理的繁琐 . RequestBodyAdvice仅对使用了@RqestBody注解的生效 , 因为它原理上还是AOP , 所以GET方法是不会操作的. package com.xbz.common.web;

  • 一篇文章带了解如何用SpringBoot在RequestBody中优雅的使用枚举参数

    目录 确认需求 定义枚举和对象 实现转换逻辑 方案一:精准攻击 方案二:全范围攻击 测试 总结 确认需求 需求与前文类似,只不过这里需要是在 RequestBody 中使用.与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 json 或 xml 格式,Spring 默认借助 Jackson 反序列化为对象. 同样的,我们需要在枚举中定义 int 类型的 id.String 类型的 code,id 取值不限于序号(即从 0 开始的 orinal 数据),code 不限于

  • Springboot拦截器如何获取@RequestBody参数

    Springboot拦截器获取@RequestBody参数 HttpContextUtils import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader;

  • Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解

    在优雅的使用枚举参数(原理篇)中我们聊过,Spring对于不同的参数形式,会采用不同的处理类处理参数,这种形式,有些类似于策略模式.将针对不同参数形式的处理逻辑,拆分到不同处理类中,减少耦合和各种if-else逻辑.本文就来扒一扒,RequestBody参数中使用枚举参数的原理. 找入口 对 Spring 有一定基础的同学一定知道,请求入口是DispatcherServlet,所有的请求最终都会落到doDispatch方法中的ha.handle(processedRequest, respons

  • 关于Springboot | @RequestBody 接收到的参数对象属性为空的问题

    背景 今天在调试项目的时候遇到一个坑,用Postman发送一个post请求,在Springboot项目使用@RequestBody接收时参数总是报不存在,但是多次检查postman上的请求格式以及项目代码都没有问题 Postman: 请求参数: { "firstName":"fdsaf", "lastName":"dfasdf" } Controller: Entity 通过debug模式可以发现传进到实体的参数都为null

  • SpringBoot @RequestParam、@PathVaribale、@RequestBody实战案例

    实例User package com.iflytek.odeon.shipper.model.rx; import io.swagger.annotations.ApiModelProperty; public class Student { @ApiModelProperty(value = "名称", example = "zhangsan", required = true) private String name; private Integer call;

  • SpringBoot在RequestBody中使用枚举参数案例详解

    前文说到 优雅的使用枚举参数 和 实现原理,本文继续说一下如何在 RequestBody 中优雅使用枚举. 本文先上实战,说一下如何实现.在 优雅的使用枚举参数 代码的基础上,我们继续实现. 确认需求 需求与前文类似,只不过这里需要是在 RequestBody 中使用.与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 json 或 xml 格式,Spring 默认借助 Jackson 反序列化为对象. 同样的,我们需要在枚举中定义 int 类型的 id.String

  • SpringBoot之使用枚举参数案例详解

    接口开发过程中不免有表示类型的参数,比如 0 表示未知,1 表示男,2 表示女.通常有两种做法,一种是用数字表示,另一种是使用枚举实现. 使用数字表示就是通过契约形式,约定每个数字表示的含义,接口接收到参数,就按照约定对类型进行判断,接口维护成本比较大. 在 Spring 体系中,使用枚举表示,是借助 Spring 的 Converter 机制,可以将数字或字符串对应到枚举的序号或者 name,然后将前端的输入转换为枚举类型. 在场景不复杂的场景中,枚举可以轻松胜任. 于是,迅速实现逻辑,准备提

  • Java reservedcodecachesize虚拟机参数案例详解

    一.reservedcodecachesize参数介绍 该参数是JvM虚拟机调优中调整内存大小的一个设置参数,值得大小设置直接影响到Code Cache的大小,而jvm编译的代码有常常存放在Code Cache中,而Code Cache的空间内存又支撑着jvm的正常运行,如果该空间不足jvm虚拟机将会发生问题,并且性能持续降低. Code Cache就是所谓的代码缓存,由于JVM虚拟机的内存默认是有大小限制的,因此代码缓存区域肯定也是有一定大小限制,一般的Windows电脑上64位系统下它的默认

  • JavaScript 中this指向问题案例详解

    总结 全局环境 ➡️ window 普通函数 ➡️ window 或 undefined 构造函数 ➡️ 构造出来的实例 箭头函数 ➡️ 定义时外层作用域中的 this 对象的方法 ➡️ 该对象 call().apply().bind() ➡️ 第一个参数 全局环境 无论是否在严格模式下,this 均指向 window 对象. console.log(this === window) // true // 严格模式 'use strict' console.log(this === window

  • React 中 setState 的异步操作案例详解

    目录 前言 React 中的 setState 为什么需要异步操作? 什么时候setState会进行同步操作? 前言 在使用state的时候, 如果我们企图直接修改state中的某一个值之后直接打印(使用)他,就会发现,他其实并没有改变. 就像下面的例子,企图通过点击事件之后就使用修改之后的state的值,但是会发state中的并没有被立即修改,还是原先的值,我们都知道那是因为 setState就相当于是一个异步操作,不能立即被修改. import React, { Component } fr

  • python编程之requests在网络请求中添加cookies参数方法详解

    哎,好久没有学习爬虫了,现在想要重新拾起来.发现之前学习爬虫有些粗糙,竟然连requests中添加cookies都没有掌握,惭愧.废话不宜多,直接上内容. 我们平时使用requests获取网络内容很简单,几行代码搞定了,例如: import requests res=requests.get("https://cloud.flyme.cn/browser/index.jsp") print res.content 你没有看错,真的只有三行代码.但是简单归简单,问题还是不少的. 首先,这

  • spring boot中的properties参数配置详解

    application.properties application.properties是spring boot默认的配置文件,spring boot默认会在以下两个路径搜索并加载这个文件 src\main\resources src\main\resources\config 配置系统参数 在application.properties中可配置一些系统参数,spring boot会自动加载这个参数到相应的功能,如下 #端口,默认为8080 server.port=80 #访问路径,默认为/

  • 对tensorflow中的strides参数使用详解

    在二维卷积函数tf.nn.conv2d(),最大池化函数tf.nn.max_pool(),平均池化函数 tf.nn.avg_pool()中,卷积核的移动步长都需要制定一个参数strides(步长),因为无论是卷积操作还是各种类型的池化操作,都是某种形式的滑动窗口(sliding window)处理,这就要求指定从当前窗口移动下一个窗口位置的移动步长. TensorFlow 文档关于 strides的说明如下: strides: A list of ints that has length >=

  • Web应用中设置Context Path案例详解

    URL:http://hostname.com/contextPath/servletPath/pathInfo Jetty 如果没有contextPath,则默认使用root上下文,root上下文的路径为"/". warName.war 在没有XML IoC文件的情况下: 如果WAR文件名是myapp.war,那么上下文路径是:/myapp: 如果WAR文件名是ROOT.war,那么上下文路径是:/: 如果WAR文件名是ROOT-foobar.war,那么上下文路径是/,虚拟host

  • Java list与set中contains()方法效率案例详解

    list.contains(o) :遍历集合所有元素,用每个元素和传入的元素进行 equals 比较,如果集合元素有 n 个,则会比较 n 次,所以时间复杂度为 O(n) .方法源码如下: // ArrayList 中的方法 public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size;

随机推荐