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

Я ищу самый простой и простой способ привязки и преобразования данных в 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... Связывание намного проще и быстрее.

Ответ 1

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

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

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

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

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

Ответ 2

  • Для конверсий String для конвертирования используются конвертеры (реализация org.springframework.format.Formatter) вместо конвертеров. Он имеет методы print (...) и parse (...), поэтому вам нужен только один класс вместо двух. Чтобы зарегистрировать их, используйте FormattingConversionServiceFactoryBean, который может регистрировать как конвертеры, так и форматировщики вместо ConversionServiceFactoryBean.
  • Новый материал Formatter имеет несколько дополнительных преимуществ:
    • Интерфейс Formatter предоставляет объект Locale в его методах печати (...) и parse (...), поэтому преобразование строк может быть чувствительным к языку
    • В дополнение к предварительно зарегистрированным форматам FormattingConversionServiceFactoryBean поставляется с несколькими удобными предварительно зарегистрированными объектами AnnotationFormatterFactory, которые позволяют вам указывать дополнительные параметры форматирования с помощью аннотации. Например: @RequestParam @DateTimeFormat (pattern = "MM-dd-yy" ) LocalDate baseDate... Создавать собственные классы AnnotationFormatterFactory не очень сложно, см. Spring NumberFormatAnnotationFormatterFactory для простого примера. Я думаю, что это устраняет необходимость в форматировщиках/редакторах, специфичных для контроллера. Используйте один ConversionService для всех контроллеров и настройте форматирование с помощью аннотаций.
  • Я согласен с тем, что, если вам по-прежнему требуется некоторое преобразование строк в контроллере, самый простой способ - использовать редактор настраиваемых свойств. (Я попытался вызвать "binder.setConversionService(...)" в моем методе @InitBinder, но он терпит неудачу, поскольку объект связующего объекта имеет уже установленную "глобальную" службу преобразования. Кажется, что классы преобразования для каждого контроллера обескуражены Spring 3).

Ответ 3

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

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

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

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

Ответ 4

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

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) { ... }
    }
}

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