如何编写一个通用的 Spring 转换器,将字符串转换为实现给定接口的枚举?

Jam*_*mes 5 java generics enums spring spring-mvc

正如如何获取枚举的 valueOf 和值并在定义为泛型类 param 时调用其实现的接口上的方法中提到的,我想将字符串转换为枚举以获得一组枚举类型。虽然那篇文章中的答案解决了我的编译问题,但 Spring 没有转换(请参阅下面的更新)。以下是该帖子中的信息,供参考:

我使用一个接口来标记要转换的枚举集,并定义转换期间使用的几个方法:

public interface SymbolEnum {
    String getCode();       
    String getDescription();
}
Run Code Online (Sandbox Code Playgroud)

下面是我想要从字符串派生的枚举类型之一的示例:

@RequiredArgsConstructor // using lombok
public enum Element implements SymbolEnum {

    IRON("FE", "iron"), 
    HYDROGEN("H", "hydrogen"),

    @Getter
    private final String code;
    @Getter
    private final String description;

}
Run Code Online (Sandbox Code Playgroud)

如果字符串与枚举常量名称、枚举代码属性或枚举描述属性匹配,那么我想转换为该枚举。否则,代码应该抛出 IllegalArgumentException。

我正在使用 Spring 的 Converter 接口来实现我的转换器:

@RequiredArgsConstructor
public class StringToSymbolEnum<T extends Enum<T> & SymbolEnum> implements Converter<String, T> {

    private final T[] values;

    @Override
    public T convert(String source) {
        try {
            return Enum.valueOf(values[0].getDeclaringClass(), source);
        } catch (IllegalArgumentException notEnumConstant) {
            for (T value : values()) {
                if (value.getDescription().equalsIgnoreCase(source) 
                        || value.getCode().equalsIgnoreCase(source)) { 
                    return value;
                }
            }
            throw notEnumConstant;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

这是我注册转换器的地方:

@Configuration
public class WebConfig implements WebMvcConfigurer {

      @Override
        public void addFormatters(FormatterRegistry registry) {

          registry.addConverter(new StringToSymbolEnum<Element>(Element.values()));
        }           
}
Run Code Online (Sandbox Code Playgroud)

但转换器从未被调用。如何让 Spring 使用转换器?

更新

我希望当向控制器中的以下端点发出 HTTP GET 请求时调用转换器:

@RestController
@RequiredArgsConstructor
@RequestMapping("/element")
public class ElementController {

    @NonNull
    private final ElementService elementService;

    @GetMapping
    public Element getElement(
            @RequestParam(name="element")
            Element element // During Spring's binding, I expect the registered converter to be called
            ) {
        return elementService.getElement(element);
    }

}
Run Code Online (Sandbox Code Playgroud)

更新2

我找到了解决这个问题的几个方法。一种解决方案是实现 SpringConverterFactory并将其注册到FormatterRegistry. 但是,该解决方案似乎更适合注册单个超类型。在我的示例中,如果类有两种类型,则需要注册SymbolEnumEnum。这是我对这种方法的尝试:

public class StringToEnumConverterFactory <R extends Enum<R> & SymbolEnum> 
    implements ConverterFactory<String, R> {

    @Override
    public <T extends R> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToSymbolEnumInner<T>(targetType.getEnumConstants());
    }

    private class StringToSymbolEnumInner <W extends R>
        implements Converter<String, W> {

        private final W[] values;

        public StringToSymbolEnumInner(W[] values) {
            this.values = values;
        }

        @SuppressWarnings("unchecked")
        @Override
        public W convert(String source) {
            log.info("called converter with values ");
            try {
                return (W) Enum.valueOf(values[0].getDeclaringClass(), source);
            } catch (IllegalArgumentException notEnumConstant) {
                for (W value : values) {
                    if (value.getDescription().equalsIgnoreCase(source)
                            || value.getCode().equalsIgnoreCase(source)) {
                        return value;
                    }
                }
                throw notEnumConstant;
            }
        }

    }
Run Code Online (Sandbox Code Playgroud)

我不喜欢演员阵容,而且它真的不能像工厂一样工作,因为我必须注册每个SymbolEnum班级。

另一种选择是使用更明确的注册,如下所示:

registry.addConverter(String.class, 
                    Element.class,
                    new StringToSymbolEnum<Element>(
                            Element.values()));
Run Code Online (Sandbox Code Playgroud)

这两种方法都有效。也就是说,转换器被调用。有更好的方法吗?为什么我的第一次尝试没有成功?我想知道这是否与类型擦除有关。