服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 一篇文章带了解如何用SpringBoot在RequestBody中优雅的使用枚举参数

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

2021-12-03 14:13看山 Java教程

这篇文章主要介绍了SpringBoot中RequestBodyAdvice使用枚举参数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

确认需求

需求与前文类似,只不过这里需要是在 RequestBody 中使用。与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 json 或 xml 格式,Spring 默认借助 Jackson 反序列化为对象。

同样的,我们需要在枚举中定义 int 类型的 id、String 类型的 code,id 取值不限于序号(即从 0 开始的 orinal 数据),code 不限于 name。客户端请求过程中,可以传 id,可以传 code,也可以传 name。服务端只需要在对象中定义一个枚举参数,不需要额外的转换,即可得到枚举值。

好了,接下来我们定义一下枚举对象。

定义枚举和对象

先定义我们的枚举类GenderIdCodeEnum,包含 id 和 code 两个属性:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 数据的请求体:

?
1
2
3
4
5
6
@Data
public class GenderIdCodeRequestBody {
    private String name;
    private GenderIdCodeEnum gender;
    private long timestamp;
}

除了GenderIdCodeEnum参数外,其他都是示例,所以随便定义一下。

实现转换逻辑

前奏铺垫好,接下来入正题了。Jackson 提供了两种方案:

  • 方案一:精准攻击,指定需要转换的字段,不影响其他类对象中的字段
  • 方案二:全范围攻击,所有借助 Jackson 反序列化的枚举字段,全部具备自动转换功能

方案一:精准攻击

这种方案中,我们首先需要实现JsonDeserialize抽象类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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注解,比如:

?
1
2
@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方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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方法上加上注解即可:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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 方法:

?
1
2
3
4
5
@PostMapping("gender-id-code-request-body")
public GenderIdCodeRequestBody bodyGenderIdCode(@RequestBody GenderIdCodeRequestBody genderRequest) {
    genderRequest.setTimestamp(System.currentTimeMillis());
    return genderRequest;
}

然后定义测试用例,还是借助 JUnit5:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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 的反序列化扩展,可以定制类型转换逻辑。

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注服务器之家的更多内容!

原文链接:https://blog.csdn.net/liuxinghao/article/details/119881628

延伸 · 阅读

精彩推荐
  • Java教程小米推送Java代码

    小米推送Java代码

    今天小编就为大家分享一篇关于小米推送Java代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    富贵稳中求8032021-07-12
  • Java教程Java BufferWriter写文件写不进去或缺失数据的解决

    Java BufferWriter写文件写不进去或缺失数据的解决

    这篇文章主要介绍了Java BufferWriter写文件写不进去或缺失数据的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java实现抢红包功能

    Java实现抢红包功能

    这篇文章主要为大家详细介绍了Java实现抢红包功能,采用多线程模拟多人同时抢红包,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中S...

    阿杜7482021-02-04
  • Java教程升级IDEA后Lombok不能使用的解决方法

    升级IDEA后Lombok不能使用的解决方法

    最近看到提示IDEA提示升级,寻思已经有好久没有升过级了。升级完毕重启之后,突然发现好多错误,本文就来介绍一下如何解决,感兴趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程xml与Java对象的转换详解

    xml与Java对象的转换详解

    这篇文章主要介绍了xml与Java对象的转换详解的相关资料,需要的朋友可以参考下...

    Java教程网2942020-09-17
  • Java教程20个非常实用的Java程序代码片段

    20个非常实用的Java程序代码片段

    这篇文章主要为大家分享了20个非常实用的Java程序片段,对java开发项目有所帮助,感兴趣的小伙伴们可以参考一下 ...

    lijiao5352020-04-06
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    这篇文章主要介绍了Java使用SAX解析xml的示例,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下...

    大行者10067412021-08-30