Как написать обобщенный c Spring конвертер, который преобразует строки в перечисление, реализующее данный интерфейс? - PullRequest
2 голосов
/ 17 января 2020

Как уже упоминалось в Как получить valueOf & значения Enum и вызвать методы в интерфейсе, который он реализует, когда он определен как универсальный c параметр класса , я хочу преобразовать строки в Enums для набора перечислимых типов. Хотя ответ в этом посте решил мои проблемы с компиляцией, Spring не конвертирует (см. Обновление ниже). Вот информация из этого поста для справки:

Я использую интерфейс, чтобы отметить, какой набор перечислений для преобразования, и определить пару методов, используемых во время преобразования:

public interface SymbolEnum {
    String getCode();       
    String getDescription();
}

Вот пример один из типов Enum, который я хочу получить из строки:

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

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

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

}

Если строка соответствует имени константы перечисления, свойству кода перечисления или свойству описания перечисления, то я хочу преобразовать в это перечисление , В противном случае код должен вызвать исключение 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;
        }
    }

}

Здесь я зарегистрировал конвертер:

@Configuration
public class WebConfig implements WebMvcConfigurer {

      @Override
        public void addFormatters(FormatterRegistry registry) {

          registry.addConverter(new StringToSymbolEnum<Element>(Element.values()));
        }           
}

Но преобразователь никогда не вызывается. Как заставить 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);
    }

}

Обновление 2

Я нашел пару решений этой проблемы. Одним из решений является реализация Spring ConverterFactory и регистрация его в FormatterRegistry. Однако это решение представляется более подходящим для регистрации одного супертипа. В моем примере у меня есть классы, которые необходимо зарегистрировать, если они бывают двух типов, SymbolEnum и Enum. Вот моя попытка такого подхода:

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;
            }
        }

    }

Мне не нравится актерский состав, и он действительно не работает как фабрика, так как я должен зарегистрировать каждый SymbolEnum класс.

Другой вариант заключается в использовании более явной регистрации следующим образом:

registry.addConverter(String.class, 
                    Element.class,
                    new StringToSymbolEnum<Element>(
                            Element.values()));

Оба эти подхода работают. То есть конвертер вызывается. Есть ли лучший подход к этому? Почему моя первая попытка не работает? Мне интересно, имеет ли это отношение к стиранию типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...