Почему для @Value используются PropertyEditors, а не Converter?
Добрый день!
В документации сказано, что для преобразование значение из @Value в необходимый тип поля используется ConversionService. Для преобразования значения из @Value в кастомный тип необходимо добавить свой Converter в ConversionService (например, в DefaultConvetsionService).
На деле столкнулся, что при преобразование String значения из @Value в long значение поля, выбрасывается исключение, трассировка для которого выглядит так (неполная)
java.lang.NumberFormatException: For input string: "AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:785) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject
Отсюда видно, что AutowiredAnnotationBeanPostProcessor использует DefaultListableBeanFactory, который вызывает TypeConverterDelegate, который в свою очередь использует CustomNumberEditor. Никакого ConversionService нет, как об этом пишут в документации.
Использую Spring Boot 3.5.6
Spring использует PropertyEditors для обработки @Value аннотаций по историческим причинам и из-за внутренней реализации механизма внедрения зависимостей. Это связано с тем, что Spring Framework постепенно эволюционировал от PropertyEditors к Converters, но для обратной совместимости и в определенных сценариях PropertyEditors все еще задействуются.
Содержание
- Исторический контекст эволюции системы преобразования типов
- Внутренняя реализация @Value и роль ConversionService
- Почему PropertyEditors все еще используются
- Конфигурация ConversionService для @Value
- Решение проблемы с Spring Boot
- Сравнение подходов
- Рекомендации по использованию
Исторический контекст эволюции системы преобразования типов
PropertyEditors являются частью JavaBeans спецификации, которая существовала задолго до появления Spring. Spring Framework изначально строился поверх этой инфраструктуры, и PropertyEditors стали стандартным способом преобразования строковых значений в другие типы данных.
PropertyEditors были определены в JavaBeans спецификации и изначально использовались Spring для преобразования текстовых значений в типы объектов.
С появлением Spring 3.0 был введен более современный подход - Converters. Converters решали несколько ключевых проблем PropertyEditors:
- Безопасность для многопоточности: PropertyEditors являются stateful (состоятельными) и не потокобезопасными, что создавало проблемы в многопоточных средах
- Гибкость: Converters могут преобразовывать между любыми типами, а не только между String и Object
- Четкая типизация: Converters явно указывают исходный и целевой типы в сигнатуре метода
Несмотря на эти преимущества, Spring сохранил поддержку PropertyEditors для обратной совместимости.
Внутренняя реализация @Value и роль ConversionService
Как видно из вашей трассировки стека, AutowiredAnnotationBeanPostProcessor использует DefaultListableBeanFactory, который вызывает TypeConverterDelegate. Это ключевой момент понимания:
// Из вашего стека вызовов:
AutowiredAnnotationBeanPostProcessor
-> DefaultListableBeanFactory
-> TypeConverterDelegate
-> CustomNumberEditor
Важно отметить, что хотя документация упомина ConversionService, внутренняя реализация Spring использует более сложную логику:
- AutowiredAnnotationBeanPostProcessor обрабатывает @Value аннотации
- DefaultListableBeanFactory разрешает зависимости через TypeConverterDelegate
- TypeConverterDelegate использует комбинацию PropertyEditors и Converters
Как указано в официальной документации Spring: “A Spring BeanPostProcessor uses a ConversionService behind the scenes to handle the process for converting the String value in @Value to the target type”. Однако на практике это происходит через промежуточный слой TypeConverterDelegate.
Почему PropertyEditors все еще используются
Основные причины, почему PropertyEditors задействуются вместо Converters в вашем случае:
1. Встроенные преобразования для стандартных типов
Для стандартных типов данных (как long в вашем случае) Spring использует встроенные PropertyEditors, такие как CustomNumberEditor. Это происходит потому, что:
- Встроенные преобразования для примитивных типов и их оберток уже реализованы через PropertyEditors
- Для простых типов не требуется регистрация дополнительных конвертеров
- Система PropertyEditors глубоко интегрирована в инфраструктуру Spring
2. Порядок разрешения преобразований
Spring использует следующий порядок для разрешения преобразований:
- Специфичные PropertyEditors - для конкретных типов
- Конфигурируемые PropertyEditors - через CustomEditorConfigurer
- ConversionService - как основной механизм
- Стандартные преобразования - через PropertyEditors
В вашем случае CustomNumberEditor срабатывает на шаге 1, так как это встроенный преобразователь для числовых типов.
3. Обратная совместимость
Spring Framework сохраняет поддержку старого подхода для обеспечения совместимости с существующими проектами и библиотеками.
Конфигурация ConversionService для @Value
Чтобы заставить Spring использовать Converters вместо PropertyEditors для @Value аннотаций, необходимо правильно настроить ConversionService:
1. Создание кастомного Converter
public class StringToLongConverter implements Converter<String, Long> {
@Override
public Long convert(String source) {
if (source == null || source.trim().isEmpty()) {
return null;
}
try {
return Long.parseLong(source);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid long value: " + source, e);
}
}
}
2. Регистрация ConversionService
@Configuration
public class ConversionConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService =
new DefaultFormattingConversionService();
conversionService.addConverter(new StringToLongConverter());
return conversionService;
}
}
3. Включение в Spring MVC конфигурацию
Если вы используете Spring MVC, убедитесь, что ConversionService правильно настроен:
<mvc:annotation-driven conversion-service="conversionService"/>
Решение проблемы с Spring Boot
В Spring Boot ситуация упрощается благодаря автоматической конфигурации:
1. Автоматическая регистрация Converters
Spring Boot автоматически регистрирует Converters, помеченные специальными аннотациями:
@ConfigurationPropertiesBinding
public class StringToLongConverter implements Converter<String, Long> {
// реализация convert()
}
2. Настройка через application.properties
Для простых случаев можно использовать встроенные механизмы Spring Boot:
# Включение автоматической конфигурации ConversionService
spring.mvc.converters.preferred-json-mapper=jackson
3. Использование @ConfigurationProperties
Альтернативный подход - использовать @ConfigurationProperties вместо @Value:
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private long userFileMinSize;
// геттеры и сеттеры
}
Сравнение подходов
| Характеристика | PropertyEditors | Converters |
|---|---|---|
| Потокобезопасность | ❌ Не потокобезопасны | ✅ Потокобезопасны |
| Типы преобразования | String ↔ Object | Любые типы ↔ Любые типы |
| Область применения | JavaBeans, Spring Core | Spring Core, Spring MVC |
| Конфигурация | Через CustomEditorConfigurer | Через ConversionService |
| Поддержка в @Value | ✅ Да | ✅ Да (требует настройки) |
| Производительность | Ниже | Выше |
Рекомендации по использованию
1. Для стандартных типов
Используйте встроенные механизмы Spring. Для числовых типов, как в вашем случае, лучше использовать:
@Value("${user-file-min-size:0}")
private long userFileMinSize;
2. Для кастомных типов
При работе с кастомными типами используйте Converters:
// 1. Создайте Converter
public class StringToMyTypeConverter implements Converter<String, MyType> {
@Override
public MyType convert(String source) {
// логика преобразования
}
}
// 2. Зарегистрируйте с @ConfigurationPropertiesBinding
@ConfigurationPropertiesBinding
@Bean
public Converter<String, MyType> myTypeConverter() {
return new StringToMyTypeConverter();
}
3. Для сложных сценариев
Если вам нужна сложная логика преобразования, рассмотрите использование Formatter:
public class MyTypeFormatter implements Formatter<MyType> {
@Override
public MyType parse(String text, Locale locale) {
// логика разбора
}
@Override
public String print(MyType object, Locale locale) {
// логика форматирования
}
}
4. Для Spring Boot приложений
Используйте автоматическую конфигурацию Spring Boot максимально эффективно. Spring Boot автоматически обнаруживает и регистрирует Converters, помеченные аннотацией @ConfigurationPropertiesBinding.
Источники
- Spring Framework Reference - Type Conversion
- Spring Framework Reference - Using @Value
- Stack Overflow - Spring MVC type conversion: PropertyEditor or Converter?
- TheServerSide - Spring Converters and Formatters
- Prasanth Gullapalli - Type Conversion in Spring
- Stack Overflow - What is the difference between PropertyEditor, Formatter and Converter in Spring?
- LogicBig - Spring - Property Editors
- Spring Boot Features - Properties Conversion
Заключение
-
PropertyEditors используются для @Value из-за исторической совместимости и глубокой интеграции в инфраструктуру Spring, особенно для стандартных типов данных.
-
ConversionService действительно задействуется Spring за кулисами, но через промежуточный слой TypeConverterDelegate, который может использовать PropertyEditors для встроенных преобразований.
-
Для кастомных типов рекомендуется использовать Converters, которые потокобезопасны и более гибки, но требуют правильной конфигурации через ConversionService.
-
Spring Boot упрощает процесс автоматической регистрацией Converters через аннотацию @ConfigurationPropertiesBinding.
-
Выбор подхода зависит от сложности преобразования: для простых типов можно использовать встроенные механизмы, для сложных - реализовывать собственные Converters.