《Spring-MVC请求入参类型转换原理》 一文中,从源码的角度解析了 Spring MVC 针对两种入参类型(表单入参,Json格式入参)如何对入参进行类型转换。

下面通过简单的案例看看在项目中如何通过自定义类型转换类使得代码更整洁。

一、背景问题

将入参为保留两位小数单位为元的数字字符串,通过 Long 类型,单位为分 进行接收。

在金融项目中,存在大量针对数值的操作,例如:数据库中存入的数值为 long 类型(以分为单位),而前端传入操作值是单位为元的字符串。(在返回给前端的时候又需要转化为元)。

针对该场景,可以定义一个工具类对数值在代码中进行转换操作,但是当数值字段多的时候,这些操作会变得繁琐。

通过自定义 类型转换,简化使用工具类的频繁操作。

1.1、表单入参 类型转换

1.1.1、分析

思考:在看完源码后,如何实现一个自定义的表单入参类型转换类?

在 Spring 中,给我们提供了想成的参考实现对象 @DateTimeFormat。

通过查看代码能够得知 @DateTimeFormat 功能的实现定义了三个类:

  • DateFormatter

    进行类型转化处理操作的类

  • DateTimeFormat

    注解类,用来标注需要被处理的字段

  • DateTimeFormatAnnotationFormatterFactory

    工厂类:用来获取 DateFormatter 处理类

@DateTimeFormat 通过定义三个类来实现 “时间类型” 入参的类型转化功能,但是还有重要的一点,就是需要将实现工厂 DateTimeFormatAnnotationFormatterFactory 注册到 Spring MVC 的处理器类 WebConversionService 中。

Spring Boot 中,在自动配置类 WebMvcAutoConfiguration 对该类进行了注册。

1.1.2、实现

注解类:DollarToCentFormat

用户对入参映射字段进行标注,被标注的字段才会被匹配到该类型转换

  1. /**
  2. * <p> Formatter 注解 </p>
  3. *
  4. * @Author WTF名字好难取
  5. * @Since V1.0
  6. */
  7. @Documented
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
  10. public @interface DollarToCentFormat {
  11. }

处理类:DollarToCentFormatter

完成 “字符串数值 -> long 类型整数值” 的功能实现

  1. /**
  2. * <p> Format 处理器 </p>
  3. *
  4. * @Author WTF名字好难取
  5. * @Since V1.0
  6. */
  7. public class DollarToCentFormatter implements Formatter<Long> {
  8. @Override
  9. public Long parse(String dollar, Locale locale) throws ParseException {
  10. if(StringUtils.isNotBlank(dollar)){
  11. BigDecimal bigDecimal = BigDecimal.valueOf(Double.valueOf(dollar)).multiply(BigDecimal.valueOf(100)).setScale(0);
  12. return Long.valueOf(bigDecimal.toString());
  13. }
  14. return 0L;
  15. }
  16. @Override
  17. public String print(Long object, Locale locale) {
  18. return object.toString();
  19. }
  20. }

工厂类:DollarToCentFormatAnnotationFormatterFactory

获取 处理类 DollarToCentFormatter 的工厂

  1. /**
  2. * <p> Formatter 处理工厂 </p>
  3. *
  4. * @Author WTF名字好难取
  5. * @Since V1.0
  6. */
  7. public class DollarToCentFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
  8. implements AnnotationFormatterFactory<DollarToCentFormat> {
  9. @Override
  10. public Set<Class<?>> getFieldTypes() {
  11. return new HashSet<Class<?>>(){{
  12. add(String.class);
  13. add(Long.class);
  14. }};
  15. }
  16. @Override
  17. public Printer<?> getPrinter(DollarToCentFormat annotation, Class<?> fieldType) {
  18. DollarToCentFormatter formatter = new DollarToCentFormatter();
  19. return formatter;
  20. }
  21. @Override
  22. public Parser<?> getParser(DollarToCentFormat annotation, Class<?> fieldType) {
  23. DollarToCentFormatter formatter = new DollarToCentFormatter();
  24. return formatter;
  25. }
  26. }

关键步骤:将 工厂类,注册到 Spring MVC 配置中

  1. /**
  2. * <p> web 配置类 </p>
  3. *
  4. * @Author WTF名字好难取
  5. */
  6. @Configuration
  7. public class WebMvcConfig implements WebMvcConfigurer {
  8. @Override
  9. public void addFormatters(FormatterRegistry registry) {
  10. registry.addFormatterForFieldAnnotation(new DollarToCentFormatAnnotationFormatterFactory());
  11. }
  12. }

1.1.3、功能演示

演示代码06-01.png

关键点:在需要使用 DollarToCentFormatter 进行类型转化的字段上使用自定义注解 @DollarToCentFormat 进行标注。

演示结果

06-02.png
演示结果:入参为:2.55 输出结果为:255

1.2、Json 格式入参 类型转换

1.2.1、分析

Spring Boot 默认使用 JackSon 框架对 Json 入参进行 反序列化操作,所以实现只需要符合 JackSon 写法即可。

知识点:当切换其他 Json 框架,如 FastJson 时,可以通过 FastJson 的特定实现方式,进行实现。

1.2.2、实现

反序列化类:DollarToCentDeserializer

完成对 Json 入参的反序列化操作

  1. /**
  2. * <p> 自定义 反序列化类 </p>
  3. *
  4. * @Author WTF名字好难取
  5. * @Since V1.0
  6. */
  7. public class DollarToCentDeserializer extends JsonDeserializer<Long> {
  8. @Override
  9. public Long deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
  10. ObjectMapper mapper = new ObjectMapper();
  11. String valueStr = mapper.readValue(jsonParser, String.class);
  12. if(StringUtils.isNotBlank(valueStr)){
  13. BigDecimal cent = BigDecimal.valueOf(Double.valueOf(valueStr)).multiply(BigDecimal.valueOf(100)).setScale(0);
  14. return Long.valueOf(cent.toString());
  15. }
  16. return 0L;
  17. }
  18. }

1.2.3、演示

演示代码

06-03.png
代码逻辑:对入参进行乘法操作。

关键点

  • 1、请求头中的 Content-Type 必须符合 application/json 或者 application/ + json (在源码中,能够得到该结果*)
  • 2、Json 格式入参接收需要使用 @RequestBody
  • 3、在需要进行反序列化类型转换的字段上加 @JsonDeserialize(using = DollarToCentDeserializer.class)

    演示结果

    06-04.png
    演示结果:入参:2.55 结果:255 * 3 = 765

    二、扩展:JackSon 返回结果值类型转换

    2.1、背景问题

    解决了入参从 元 -> 分 的,但是针对前端展示,需要在返回给前端值实现 分 -> 元

2.2、分析

区别于 JackSon 入参处理,入参处理使用的反序列化,将入参映射到接收对象中,需要实现 JsonDeserializer

而对于结果的返回,是对对象的 Json 格式序列化操作,需要实现 JsonSerializer 。

2.3、实现

Json 序列化类:CentToDollarSerializer

实现对象 -> Jsno 字符串 时的类型转换 该操作在 HandlerMethodReturnValueHandler 的实现类 RequestResponseBodyMethodProcessor 完成操作。 友链:Spring-MVC【源码篇】请求参数和响应结果解析

  1. /**
  2. * <p> 分 转 元 序列化 </p>
  3. *
  4. * @Author WTF名字好难取
  5. * @Since V1.0
  6. */
  7. public class CentToDollarSerializer extends JsonSerializer<Long> {
  8. @Override
  9. public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  10. if (null != value) {
  11. String num = BigDecimal.valueOf(value).divide(BigDecimal.valueOf(100)).setScale(2).toString();
  12. gen.writeString(num);
  13. } else {
  14. gen.writeString("");
  15. }
  16. }
  17. }

2.4、演示

演示代码

06-05.png
代码逻辑:对入参 乘以 3

关键点

  • 1、返回的结果必须为指定的对象。如:这里的 JsonDTO
  • 2、在需要进行序列化的字段上加 @JsonSerialize(using = CentToDollarSerializer.class)

演示结果

演示结果: 06-06.png入参:2.55 ,结果:7.65