Application.java
Код: Выделить всё
@SpringBootApplication
@EnableCaching
@EnableScheduling
@ComponentScan(basePackages = {"my.package"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Код: Выделить всё
@RestController
@RequestMapping(path = "enpoint", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public MyController {
@Autowired
private NamedParameterJdbcTemplate namedJdbcTemplate;
@PostMapping("do")
public Response doStuff(@RequestBody Request request) {
return this.namedJdbcTemplate.queryForObject(
"SELECT ONE_CHAR as value FROM TABLE WHERE KEY = :KEY",
new MapSqlParameterSource("KEY", request.getKey()),
new BeanPropertyRowMapper(Response.class)
);
}
}
Код: Выделить всё
public class Request {
private String key;
// ... constructor / getter / setters ...
}
Поскольку в таблице БД столбец ONE_CHAR может иметь только 3 возможных значения (A, B или С). Вместо того, чтобы иметь объект Response только с «частным строковым значением;», я создал MyEnum, который содержит только символы.
Код: Выделить всё
public class Response {
private MyEnum value;
// ... constructor / getter / setters ...
}
'A', 'B' и 'C' - неподходящие имена для моего перечисления значения, поэтому я помещаю эту информацию в переменную 'id' в своем перечислении.
Код: Выделить всё
public enum MyEnum implements EnumById {
GOOD_NAME("A"), NICE_NAME("B"), PERFECT_NAME("C");
private final String id;
// ... constructor / getter / setters ...
}
Я создал интерфейс EnumById, поэтому у меня есть общий способ обработки всех перечислений с помощью та же проблема (значения, которые я хочу представить в виде перечислений, не могут или не должны использоваться в качестве имени переменной).
Код: Выделить всё
public interface EnumById {
String getId();
}
- EnumById можно применять к нескольким перечислениям
- Я нужна ссылка на класс для вызова clazz.getEnumConstants()
- Я не хочу писать собственный преобразователь для каждого Enum, который будет расширять EnumById
StringToEnumByIdConverterFactory.java
Код: Выделить всё
public class StringToEnumByIdConverterFactory implements ConverterFactory {
private static class StringToEnumByIdConverter implements Converter {
private final T[] values;
public StringToEnumByIdConverter(final T[] values) {
this.values = values;
}
public T convert(final String source) {
for (final T value : values) {
if (source.equals(value.getId())) {
return value;
}
}
return null;
}
}
public Converter getConverter(final Class targetType) {
return new StringToEnumByIdConverter(targetType.getEnumConstants());
}
}
Как сообщает нам https://www.baeldung.com/spring-type-conversions Я должен добавить свою фабрику преобразователей в регистр формата Spring... поэтому я написал:
Код: Выделить всё
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new StringToEnumByIdConverterFactory());
}
}
Мое решение — удалить конвертер по умолчанию (org.springframework.core.convert.support.StringToEnumConverterFactory), поставить свой и снова добавить конвертер по умолчанию, поэтому мой класс WebConfig теперь нравится это:
WebConfig.java
Код: Выделить всё
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, Enum.class);
registry.addConverterFactory(new StringToEnumByIdConverterFactory());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
}
Теперь, когда хотя бы мое приложение заработало, я отладил его и проверил, что реестр правильно обновляется при добавлении addFormatters. вызывается.
Когда я вызываю конечную точку, по-прежнему вызывается преобразователь по умолчанию. Я получаю то же исключение, которое получил бы, если бы код, связанный с конвертером, никогда не был написан/использован/вызван:
Код: Выделить всё
org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'my.package.constants.Constants$MyEnum' for property 'value'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [my.package.constants.Constants$MyEnum] for value 'A'; nested exception is java.lang.IllegalArgumentException: No enum constant 'my.package.constants.Constants.MyEnum.A'
Я приступил к отладке и обнаружил обнаружил, что используемый ConversionService не имеет тех же преобразователей, которые я видел при отладке моего класса WebConfig (в регистре моего класса было ~ 155 преобразователей, используемый здесь ConversionService имеет всего 52). Я ясно вижу StringToEnumConverterFactory по умолчанию:

Где /Как я не могу указать правильному сервису ConversionService (их несколько?) использовать мои конвертеры?
ОБНОВЛЕНИЕ: НАЙДЕНО ПРОБЛЕМА, НО ЕЩЕ НЕРЕШЕННАЯ
Я использую новый BeanPropertyRowMapper(Response.class), который имеет собственный ConversionService:
Код: Выделить всё
private ConversionService conversionService = DefaultConversionService.getSharedInstance();ОБНОВЛЕНИЕ: НАЙДЕНО «ПОДДЕЛОЧНОЕ» РЕШЕНИЕ (ПОСКОЛЬКУ ОЧЕНЬ УЖАСНО)
Написал это перед запросом, и теперь преобразование работает... Я переместил его куда-нибудь, оно будет вызываться только один раз и все равно работает.
Код: Выделить всё
// this is what BeanPropertyRowMapper use
DefaultConversionService sharedInstance = (DefaultConversionService) DefaultConversionService.getSharedInstance();
// remove default String to Enum conversion
sharedInstance.removeConvertible(String.class, Enum.class);
// addd my converter factory
sharedInstance.addConverterFactory(new StringToEnumByCodeConverterFactory());
// add again all default converter back, since:
// - I removed only the String->Enum one
// - Converters are placed in a LinkedMap
// No duplicates are inserted, I only get that the default Enum converter is added AFTER my converter
DefaultConversionService.addDefaultConverters(sharedInstance);
// Now my converter has priority on the default Strin->Enum one and BeanPropertyRowMapper can use it
Подробнее здесь: https://stackoverflow.com/questions/667 ... ic-interfa
Мобильная версия