该文章已同步至个人微信公众号[不能止步],欢迎关注。
在WEB开发,客户端和服务端传输的数据中经常包含一些这样的字段:字段的值只包括几个固定的字符串。 这样的字段意味着我们需要在数据传输对象(Data Transfer Object, DTO)中对该字段进行校验以避免客户端传输的非法数据持久化到我们的系统中。
public record UserCreateDto(
String userName,
// userType的值为NORMAL, SILVER_CARD, GOLD_CARD,
String userType) {
}
我们可以采用多种办法验证userType的正确性,如:
方法一:利用Validation和正则表达式进行验证
public record UserCreateDto(
String userName,
// userType的值为NORMAL, SILVER_CARD, GOLD_CARD
@Pattern(regexp = "^NORMAL$|^SILVER_CARD$|^GOLD_CARD$")
String userType) {
}
方法二:在代码中写validate方法,在使用到DTO代码中调用validate方法
public record UserCreateDto(
String userName,
// userType的值为NORMAL, SILVER_CARD, GOLD_CARD
String userType) {
public void validate() {
if (List.of("NORMAL", "SILVER_CARD", "GOLD_CARD").contains(userType)) {
return;
}
throw new IllegalArgumentException("userType must be NORMAL, SILVER_CARD, GOLD_CARD");
}
}
比较这两种方法,两种方法各有优缺点:
优点 | 缺点 | |
---|---|---|
方法一 | 在DTO创建时(即参数的入口处)就可以验证数据的有效性 | 在@Pattern中使用字符串常量不方便,意味着开发者很难在整个代码中使用统一的自定义常量,为后期的修改带来不便 |
方法二 | 开发者可以在整个代码中使用统一的自定义常量,方便后续的修改 | 需要开发者主动调用validate方法,容易遗漏调用 |
可能你早已想到用枚举来解决上述场景中的问题,没错,在面向对象编程中,枚举是解决这种问题的最好的解决办法。
public enum UserType {
NORMAL, SILVER_CARD, GOLD_CARD
}
public record UserCreateDto(
String userName,
// userType的值为NORMAL, SILVER_CARD, GOLD_CARD
@NotNull
UserType userType) {
}
枚举让我们的参数具有类型约束,并且具有可复用性和易修改等特性。
但是在SpringBoot中默认是不支持String到Enum的转换(读者可以尝试一下,不管客户端传入的userType正确与否,在DTO中userType值均为null )。
为了解决这个问题很多开发者都是通过自定义Conveter来进行String到Enum的转换的。如此常见的场景,作为开发者的我们都能想到使用统一的Converter,难到作为框架的开发者想不到?
先上解决方案。
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 通过ApplicationConversionService向应用中注入Converter
ApplicationConversionService.configure(registry);
}
就是这么简单,在应用中自定义WebMvcConfigurer,覆写addFormatters方法,并通过ApplicationConversionService向应用中注入String到Enum的Converter。
通过分析ApplicationConversionService
的时序图,我们可以看到ApplicationConversionService
最终通过DefaultConversonService
调用ConverterRegister向应用注册了StringToEnumConverterFactory
,从名字可以看出来StringToEnumConverterFactory
就是负责String向Enum转换的。
StringToEnumConverterFactory
的代码如下:
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum(ConversionUtils.getEnumType(targetType));
}
private static class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
StringToEnum(Class<T> enumType) {
this.enumType = enumType;
}
@Override
@Nullable
public T convert(String source) {
if (source.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
可以看出,StringToEnumConverterFactory
中也是通过Enum的valueOf方法完成String到Enum的转换的。
采用Spring框架提供的StringToEnum Converter带给我们便利性的同时,也存在一些约束,如:
因此,选用哪种方法完成字符串到Enum的转换还要根据实际的应用场景出发。