三、通用化Converter
前言
前面的文章已经解决了处理单个枚举常量的数据转换的问题。
但这并不是最终解决方案,我们不可能为每一个枚举类都创建一套完整的转换类,所以就有了本文,最终版的解决方案
问题处理
改造枚举类
解决通用化问题,自然是要用到泛型,所以我们要为每一个枚举类抽取一个父类。
但java中的枚举类型是无法继承的,不过可以实现接口,所以我们在这里抽取一个接口出来,并让后续所有的枚举常量类都实现该接口。
改造之前,首先要明白,枚举实际上也是一个类,枚举实例也是一个对象。
所以枚举类实现了接口后,所有枚举类对象都会有该接口方法。
要抽取的内容:
首先要抽取getValue方法,因为一般情况,都要用父类引用调用子类方法,避免不必要的强制转换。
然后就是获取所有枚举类型的方法,用户遍历匹配枚举对象。
这里仅仅是我个人的思路,如果你们有更好的匹配方法,也可以自行设计。
接着可以在接口中创建一个默认方法,用于匹配枚举对象。
所以,这个接口需要有泛型,泛型就是实现该接口的枚举类型!
接口源码
/**
* @author jeff
* @since 2023/4/30 17:49
*/
public interface ConstantEnumIFace<T extends ConstantEnumIFace> {
/**
* 返回每个枚举对象的真值
* @return
*/
int getValue();
/**
* 返回每个枚举的所有枚举对象
* @return
*/
T[] theEnums();
/**
* 遍历匹配
* @param value
* @return
*/
default T theEnum(int value) {
T[] enums = theEnums();
for (T theEnum: enums) {
if (value==theEnum.getValue()){
System.out.println("已匹配到数据:"+theEnum);
return theEnum;
}
}
throw new RuntimeException("无效枚举值"+value);
}
}
这里需要声明接口泛型是继承该接口的,因为我们要调用接口中的方法,不过具体方法内容由子类编写。
子类内容
import lombok.RequiredArgsConstructor;
/**
* @author jeff
* @since 2023/4/30 17:46
*/
@RequiredArgsConstructor
public enum UserStatus implements ConstantEnumIFace<UserStatus> {
DISABLE(2,"禁用"),
ENABLE(3,"启用");
private final int value;
private final String desc;
@Override
public int getValue() {
return value;
}
@Override
public UserStatus[] theEnums() {
return UserStatus.values();
}
}
可以看到子类实现了上面的接口,并设置泛型为自身
自定义ConverterFactory
思路
查看springMVC的FormatterRegistry类的方法,可以发现有这么一个内容:
也就是提供一个工厂类,当SpringMVC需要转换某类数据时,会先判断有无该类型的Converter.
如果无,则找该类型的ConverterFactory,并执行其中的getConverter方法,传递的是该枚举类的Class对象。
所以我们让所有的枚举类实现了同一个接口,这时,我们只要设置一个该接口类型的Factory,SpringMVC为碰到的每一个枚举对象,就会调用该Facotry创建Converter。
这时,可能会有人有疑问,为什么不能使用一个Converter,匹配所有该接口的数据类型呢?
这个问题本人也考虑过,同时也实践过,发现虽然SpringMVC不会调用该Converter的convert方法,同时我们没法知道他现在正在为具体哪个枚举进行转换,所以放弃了。
编写ConverterFactory
综上所述,可以知道现在要写一个,让SpringMVC碰到枚举类后,会调用其getConverter方法的Factory类
/**
* @author jeff
* @since 2023/5/1 16:46
*/
public class ConstantEnumConverterFactory implements ConverterFactory<String, ConstantEnumIFace> {
Map<Class,StringToEnumConverter> enumMap = new HashMap<>();
public ConstantEnumConverterFactory() {
}
@Override
public <T extends ConstantEnumIFace> Converter<String, T> getConverter(Class<T> targetType) {
StringToEnumConverter stringToEnumConverter = enumMap.get(targetType);
if (null==stringToEnumConverter) {
stringToEnumConverter = new StringToEnumConverter(targetType);
enumMap.put(targetType,stringToEnumConverter);
}
return stringToEnumConverter;
}
private static class StringToEnumConverter<T extends ConstantEnumIFace<T>> implements Converter<String, T> {
private T firstEnum;
public StringToEnumConverter(Class<T> enumType) {
firstEnum=enumType.getEnumConstants()[0];
}
@Override
public T convert(String sourceStr) {
return firstEnum.theEnum(Integer.parseInt(sourceStr));
}
}
}
至于这里的getConverter方法,为什么要先从Map中获取,下文会解释的。
这时,当SpringMVC碰到ConstantEnumIFace的实现类时,会调用此Factory对象的getConverter方法,并传递实际的实现类,也就是枚举类的class对象。
这时,我们再利用该对象,去创建对应的Converter实例。
我们只要在创建Convertor时,调用类对象的getEnumConstants方法,并获取首位枚举对象供后续使用。
firstEnum = enumType.getEnumConstants()[0];
而真正的convert方法,则是使用刚刚初始化的首位枚举对象的方法 :
firstEnum.theEnum(Integer.parseInt(sourceStr));
而该方法,具体执行的是ConstantEnumIFace的默认方法。
同时该方法中的,获取所有枚举对象实例的方法,则由具体的枚举对象提供。
注册ConverterFactory
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new ConstantEnumConverterFactory());
}
}
测试
可以看到debug断点已进入接口方法,从调用栈中,也能看到SpringMVC在调用Factory的getConverter方法。
但是!这不是我第一次发起的请求,这就说明,SpringMVC每次碰到相应数据时,都会调用一次getConverter方法获取Convertor。
所以在上文中,getConverter方法要缓存一份Convertor到map中,防止每次来数据都new一个Converter方法。
总结
到这里,用枚举类接收,注意是接收Get请求的query参数的问题,就彻底解决了。
接下来就是body中Json数据了.