环境:Spring Boot 2.1.5.RELEASE 对应 Springframework 5.1.7.RELEASE
在 Spring-MVC【应用篇】请求入参类型转换 一文中,从源码的角度解析了 Spring MVC 针对两种入参类型(表单入参,Json格式入参)如何对入参进行类型转换。
下面通过简单的案例看看在项目中如何通过自定义类型转换类使得代码更整洁。
在金融项目中,存在大量针对数值的操作,例如:数据库中存入的数值为 long 类型(以分为单位),而前端传入操作值是单位为元的字符串。(在返回给前端的时候又需要转化为元)。
针对该场景,可以定义一个工具类对数值在代码中进行转换操作,但是当数值字段多的时候,这些操作会变得繁琐。
通过自定义 类型转换,简化使用工具类的频繁操作。
思考:在看完源码后,如何实现一个自定义的表单入参类型转换类?
在 Spring 中,给我们提供了想成的参考实现对象 @DateTimeFormat。
通过查看代码能够得知 @DateTimeFormat 功能的实现定义了三个类:
进行类型转化处理操作的类
注解类,用来标注需要被处理的字段
工厂类:用来获取 DateFormatter 处理类
@DateTimeFormat 通过定义三个类来实现 “时间类型” 入参的类型转化功能,但是还有重要的一点,就是需要将实现工厂 DateTimeFormatAnnotationFormatterFactory 注册到 Spring MVC 的处理器类 WebConversionService 中。
Spring Boot 中,在自动配置类 WebMvcAutoConfiguration 对该类进行了注册。
用户对入参映射字段进行标注,被标注的字段才会被匹配到该类型转换
/**
* Formatter 注解
*
* @Author WTF名字好难取
* @Since V1.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface DollarToCentFormat {
}
完成 “字符串数值 -> long 类型整数值” 的功能实现
/**
* Format 处理器
*
* @Author WTF名字好难取
* @Since V1.0
*/
public class DollarToCentFormatter implements Formatter<Long> {
@Override
public Long parse(String dollar, Locale locale) throws ParseException {
if(StringUtils.isNotBlank(dollar)){
BigDecimal bigDecimal = BigDecimal.valueOf(Double.valueOf(dollar)).multiply(BigDecimal.valueOf(100)).setScale(0);
return Long.valueOf(bigDecimal.toString());
}
return 0L;
}
@Override
public String print(Long object, Locale locale) {
return object.toString();
}
}
获取 处理类 DollarToCentFormatter 的工厂
/**
* Formatter 处理工厂
*
* @Author WTF名字好难取
* @Since V1.0
*/
public class DollarToCentFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory<DollarToCentFormat> {
@Override
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(){{
add(String.class);
add(Long.class);
}};
}
@Override
public Printer<?> getPrinter(DollarToCentFormat annotation, Class<?> fieldType) {
DollarToCentFormatter formatter = new DollarToCentFormatter();
return formatter;
}
@Override
public Parser<?> getParser(DollarToCentFormat annotation, Class<?> fieldType) {
DollarToCentFormatter formatter = new DollarToCentFormatter();
return formatter;
}
}
/**
* web 配置类
*
* @Author WTF名字好难取
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new DollarToCentFormatAnnotationFormatterFactory());
}
}
关键点:在需要使用 DollarToCentFormatter 进行类型转化的字段上使用自定义注解 @DollarToCentFormat 进行标注。
演示结果:入参为:2.55 输出结果为:255
Spring Boot 默认使用 JackSon 框架对 Json 入参进行 反序列化操作,所以实现只需要符合 JackSon 写法即可。
知识点:当切换其他 Json 框架,如 FastJson 时,可以通过 FastJson 的特定实现方式,进行实现。
完成对 Json 入参的反序列化操作
/**
* 自定义 反序列化类
*
* @Author WTF名字好难取
* @Since V1.0
*/
public class DollarToCentDeserializer extends JsonDeserializer<Long> {
@Override
public Long deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String valueStr = mapper.readValue(jsonParser, String.class);
if(StringUtils.isNotBlank(valueStr)){
BigDecimal cent = BigDecimal.valueOf(Double.valueOf(valueStr)).multiply(BigDecimal.valueOf(100)).setScale(0);
return Long.valueOf(cent.toString());
}
return 0L;
}
}
代码逻辑:对入参进行乘法操作。
关键点
演示结果:入参:2.55 结果:255 * 3 = 765
区别于 JackSon 入参处理,入参处理使用的反序列化,将入参映射到接收对象中,需要实现 JsonDeserializer。
而对于结果的返回,是对对象的 Json 格式序列化操作,需要实现 JsonSerializer 。
实现对象 -> Jsno 字符串 时的类型转换
该操作在 HandlerMethodReturnValueHandler 的实现类 RequestResponseBodyMethodProcessor 完成操作。
友链:Spring-MVC【源码篇】请求参数和响应结果解析
/**
* 分 转 元 序列化
*
* @Author WTF名字好难取
* @Since V1.0
*/
public class CentToDollarSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (null != value) {
String num = BigDecimal.valueOf(value).divide(BigDecimal.valueOf(100)).setScale(2).toString();
gen.writeString(num);
} else {
gen.writeString("");
}
}
}
代码逻辑:对入参 乘以 3
关键点:
演示结果: 入参:2.55 ,结果:7.65