好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

确认需求

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

查看更多关于一篇文章带了解如何用SpringBoot在RequestBody中优雅的使用枚举参数的详细内容...

  阅读:21次