Преобразование типа Spring MVC: PropertyEditor или Converter?

129

Я ищу самый простой и простой способ привязать и преобразовать данные в Spring MVC. Если возможно, без настройки xml.

До сих пор я использовал PropertyEditors вот так:

public class CategoryEditor extends PropertyEditorSupport {

    // Converts a String to a Category (when submitting form)
    @Override
    public void setAsText(String text) {
        Category c = new Category(text);
        this.setValue(c);
    }

    // Converts a Category to a String (when displaying form)
    @Override
    public String getAsText() {
        Category c = (Category) this.getValue();
        return c.getName();
    }

}

и

...
public class MyController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Category.class, new CategoryEditor());
    }

    ...

}

Это просто: оба преобразования определены в одном классе, и привязка проста. Если бы я хотел сделать общую привязку для всех своих контроллеров, я все равно мог бы добавить 3 строки в свою конфигурацию xml .


Но Spring 3.x представил новый способ сделать это, используя преобразователи :

В контейнере Spring эту систему можно использовать как альтернативу PropertyEditors.

Допустим, я хочу использовать конвертеры, потому что это «последняя альтернатива». Мне пришлось бы создать два конвертера:

public class StringToCategory implements Converter<String, Category> {

    @Override
    public Category convert(String source) {
        Category c = new Category(source);
        return c;
    }

}

public class CategoryToString implements Converter<Category, String> {

    @Override
    public String convert(Category source) {
        return source.getName();
    }

}

Первый недостаток: мне нужно сделать два класса. Преимущество: нет необходимости кастовать благодаря универсальности.

Тогда как мне просто привязать данные к конвертерам?

Второй недостаток: я не нашел простого способа (аннотации или других программных средств) сделать это в контроллере: ничего подобного someSpringObject.registerCustomConverter(...);.

Единственные способы, которые я нашел, были бы утомительными, не простыми и касались только общей привязки кросс-контроллера:

  • Конфигурация XML :

    <bean id="conversionService"
      class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="somepackage.StringToCategory"/>
                <bean class="somepackage.CategoryToString"/>
            </set>
        </property>
    </bean>
    
  • Конфигурация Java ( только в Spring 3.1+ ):

    @EnableWebMvc
    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Override
        protected void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new StringToCategory());
            registry.addConverter(new CategoryToString());
        }
    
    }
    

Зачем при всех этих недостатках использовать конвертеры? Я что-то упускаю ? Есть ли другие уловки, о которых я не знаю?

Я испытываю искушение продолжать использовать PropertyEditors ... Связывание намного проще и быстрее.

Джером Далберт
источник
Примечание (я тоже споткнулся, используя Spring 3.2.17): при использовании <mvc: annotation-driven /> действительно необходимо ссылаться на этот bean-компонент convertService: <mvc: annotation-driven conversion-service = "conversionService" />
mauhiz
addFormatters (...) должны быть общедоступными. Также, начиная с 5.0, WebMvcConfigurerAdapter устарел.
Пако Абато

Ответы:

55

Зачем при всех этих недостатках использовать конвертеры? Я что-то упускаю ? Есть ли другие уловки, о которых я не знаю?

Нет, я думаю, вы очень подробно описали и PropertyEditor, и Converter, как каждый из них объявляется и регистрируется.

На мой взгляд, PropertyEditors ограничены по объему - они помогают преобразовать String в тип, и эта строка обычно поступает из пользовательского интерфейса, поэтому регистрация PropertyEditor с использованием @InitBinder и использование WebDataBinder имеет смысл.

Конвертер, с другой стороны, более общий, он предназначен для ЛЮБОГО преобразования в системе, а не только для преобразований, связанных с пользовательским интерфейсом (тип String в целевой). Например, Spring Integration широко использует конвертер для преобразования полезной нагрузки сообщения в желаемый тип.

Я думаю, что для потоков, связанных с пользовательским интерфейсом, PropertyEditors по-прежнему подходят, особенно в случае, когда вам нужно сделать что-то особенное для определенного свойства команды. В других случаях я бы взял рекомендацию из справочника Spring и вместо этого написал бы конвертер (например, для преобразования из Long id в сущность, скажем, в качестве образца).

Биджу Кунджуммен
источник
5
Еще одна хорошая вещь, что преобразователи не сохраняют состояние, в то время как редакторы свойств сохраняют состояние и создаются много раз и реализуются с помощью множества вызовов API. Я не думаю, что это окажет какое-либо серьезное влияние на производительность, но преобразователи просто чище и проще.
Борис Треухов
1
@Boris cleaner да, но не проще, особенно для новичка: вам нужно написать 2 класса конвертера + добавить несколько строк в конфигурацию xml или конфигурацию java. Я говорю о отправке / отображении формы Spring MVC с общими преобразованиями (не только сущностями).
Джером Далберт
16
  1. Для преобразований в / из String используйте форматеры (реализуйте org.springframework.format.Formatter ) вместо преобразователей. У него есть методы print (...) и parse (...) , поэтому вам нужен только один класс вместо двух. Чтобы зарегистрировать их, используйте FormattingConversionServiceFactoryBean , который может регистрировать как преобразователи, так и средства форматирования, вместо ConversionServiceFactoryBean .
  2. Новый компонент Formatter имеет несколько дополнительных преимуществ:
    • Интерфейс Formatter предоставляет объект Locale в своих методах print (...) и parse (...) , поэтому преобразование строк может зависеть от языкового стандарта.
    • В дополнение к предварительно зарегистрированным средствам форматирования FormattingConversionServiceFactoryBean поставляется с несколькими удобными предварительно зарегистрированными объектами AnnotationFormatterFactory , которые позволяют вам указывать дополнительные параметры форматирования с помощью аннотации. Например: @RequestParam@DateTimeFormat (рисунок = "ММ-дд-уу")LocalDate baseDate ... Создать свои собственные классы AnnotationFormatterFactory несложно, простой пример см. В Spring NumberFormatAnnotationFormatterFactory . Я думаю, что это устраняет необходимость в форматерах / редакторах для конкретного контроллера. Используйте одну службу ConversionService для всех контроллеров и настройте форматирование с помощью аннотаций.
  3. Я согласен с тем, что если вам все еще нужно преобразование строк для конкретного контроллера, самым простым способом будет использование редактора настраиваемых свойств. (Я попытался вызвать ' binder.setConversionService (...) ' в моем методе @InitBinder , но это не удалось , поскольку объект привязки поставляется с уже установленной 'глобальной' службой преобразования. Похоже, классы преобразования для каждого контроллера не приветствуются в Весна 3).
Александр
источник
7

Самый простой (при условии, что вы используете структуру персистентности), но не идеальный способ - реализовать универсальный преобразователь сущностей через ConditionalGenericConverterинтерфейс, который будет преобразовывать сущности с использованием их метаданных.

Например, если вы используете JPA, этот преобразователь может проверять, есть ли у указанного класса @Entityаннотации, и использовать @Idаннотированное поле для извлечения информации и автоматического поиска, используя предоставленное значение String в качестве идентификатора для поиска.

public interface ConditionalGenericConverter extends GenericConverter {
    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

ConditionalGenericConverter является «абсолютным оружием» API преобразования Spring, но будет реализован, как только он сможет обрабатывать большинство преобразований сущностей, экономя время разработчика - это большое облегчение, когда вы просто указываете классы сущностей в качестве параметров вашего контроллера и никогда не думаете о реализации новый конвертер (за исключением, конечно, кастомных и не сущностных типов).

Борис Треухов
источник
Хорошее решение для работы только с преобразованием сущностей, спасибо за трюк. Не просто вначале, так как вам нужно написать еще один класс, но в конечном итоге просто и экономно по времени.
Джером Далберт
Кстати, такой преобразователь может быть реализован для любых типов, которые придерживаются некоторого общего контракта - еще один пример: если ваши перечисления реализуют какой-то общий интерфейс обратного просмотра - тогда вы также сможете реализовать общий преобразователь (он будет похож на stackoverflow.com / questions / 5178622 /… )
Борис Треухов
@JeromeDalbert да, новичку немного сложно сделать что-то тяжелое, но если у вас есть команда разработчиков, будет проще) PS И все равно будет скучно регистрировать одни и те же редакторы свойств каждый раз при привязке формы)
Борис Треухов
1

Вы можете обойти необходимость иметь два отдельных класса Converter, реализуя два преобразователя как статические внутренние классы.

public class FooConverter {
    public static class BarToBaz implements Converter<Bar, Baz> {
        @Override public Baz convert(Bar bar) { ... }
    }
    public static class BazToBar implements Converter<Baz, Bar> {
        @Override public Bar convert(Baz baz) { ... }
    }
}

Вам все равно придется зарегистрировать их оба по отдельности, но, по крайней мере, это сократит количество файлов, которые вам нужно изменить, если вы внесете какие-либо изменения.

НТМ
источник