前端传的字符串自动转换为对应枚举的方式
背景
- 在项目中我们通常会使用枚举来接收一些值固定的参数,例如此次项目中我们需要传递一个语言参数,这个参数在系统中有三个值:中文 、英文、和繁体中文,在数据库存的是相对应的Integer值
(别问我为啥还要整个小写,前端规范是小写,后段枚举规范用大写,没发用spring自带的枚举转换器。)
@Getter
@RequiredArgsConstructor
public enum LanguageType {
/**
* 中文
*/
CN("zh-cn",0),
/**
* 英文
*/
EN("en-us",1),
/**
* 繁体中文
*/
TC("zh-tw",2)
;
private final String type;
private final Integer index;
}
我们希望通过前端穿过来的字符串来找到对应的枚举。
之前我的方案是这样的
public static LanguageType get(String type) {
for (LanguageType value : LanguageType.values()) {
if (value.getType().equals(type)) {
return value;
}
}
return null;
}
前端传过来的字符串,需要我们手动使用findByType来转换为对应的枚举,这样着实有点麻烦。
我们知道spring有一个自带的枚举转换器,是否可以参照spring的方法自己也写一个转换器呢?
实现自定义枚举转换器
Converter是Spring3中引入的一项比较特殊的功能,其实就是一个转换器,可以把一种类型转换为另一种类型。可以将前端出传入的参数转换为后端可以使用的类型,如:日期格式化,字符串去空格等。
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S var1);
}
ConverterFactory是创建Converter的工厂类。
我们通过实现ConverterFactory和Converter来实现自定义枚举转换,
代码实现
@SuppressWarnings("all")
public class EnumConverterFactory implements ConverterFactory<String, Enum<?>> {
private final ConcurrentMap<Class<? extends Enum<?>>, EnumConverterHolder> holderMapper = new ConcurrentHashMap<>();
@Override
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
EnumConverterHolder holder = holderMapper.computeIfAbsent(targetType,EnumConverterHolder::createHolder);
return (Converter<String, T>) holder.converter;
}
@AllArgsConstructor
static class EnumConverterHolder {
@Nullable
final EnumMvcConverter<?> converter;
static EnumConverterHolder createHolder(Class<?> targetType) {
//获取EnumConvertMethod注解的所有方法
List<Method> methodList = MethodUtils.getMethodsListWithAnnotation(targetType, EnumConvertMethod.class, false, true);
if (CollectionUtil.isEmpty(methodList)) {
return new EnumConverterHolder(null);
}
Assert.isTrue(methodList.size() == 1, "@EnumConvertMethod 只能标记在一个工厂方法(静态方法)上");
Method method = methodList.get(0);
Assert.isTrue(Modifier.isStatic(method.getModifiers()), "@EnumConvertMethod 只能标记在工厂方法(静态方法)上");
return new EnumConverterHolder(new EnumMvcConverter<>(method));
}
}
static class EnumMvcConverter<T extends Enum<T>> implements Converter<String, T> {
private final Method method;
EnumMvcConverter(Method method) {
this.method = method;
this.method.setAccessible(true);
}
@Override
public T convert(String source) {
if (source.isEmpty()) {
// reset the enum value to null.
return null;
}
try {
return (T) method.invoke(null, Integer.valueOf(source));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
}
这里我们使用@EnumConvertMethod来标识转换的方法,然后再Holder类里面拿到这些方法反射执行。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnumConvertMethod {
}
加载配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public EnumConverterFactory enumConverterFactory() {
return new EnumConverterFactory();
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(enumConverterFactory());
}
}
使用
在该方法上加上@EnumConvertMethod
@EnumConvertMethod
public static LanguageType get(String type) {
for (LanguageType value : LanguageType.values()) {
if (value.getType().equals(type)) {
return value;
}
}
return null;
}
对请求体参数的处理
springMvc的枚举转换器只支持get请求额参数转换,如果是POST请求是无法支持的。
另外如果我们从数据库查出来返回给前端时, 还需要我们去转换为对应的字符串,这样仍然不够方便。
我们传给前端的时候是将参数通过json序列化传给前端的,我们可以使用Jackson 为我们提供了两个注解,解决这个问题。
- @JsonValue:在序列化时,只序列化 @JsonValue 注解标注的值
- @JsonCreator:在反序列化时,调用 @JsonCreator 标注的构造器或者工厂方法来创建对象
最终的代码
@EnumConvertMethod
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
@Nullable
public static LanguageType get(String type) {
for (LanguageType value : LanguageType.values()) {
if (value.getType().equals(type)) {
return value;
}
}
return null;
}