Весняне перетворення типу 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 ( лише навесні 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 /> виникає потреба фактично посилатися на цю конверсіюService: <mvc: annotation-translation-service = "convertService" />
mauhiz

addFormatters (...) повинні бути загальнодоступними. Крім того, оскільки 5.0 WebMvcConfigurerAdapter є застарілим.
Пако Абато

Відповіді:


55

З усіма цими недоліками, навіщо використовувати перетворювачі? Я щось пропускаю? Чи є інші хитрощі, про які я не знаю?

Ні, я думаю, ви дуже всебічно описали і PropertyEditor, і конвертер, як кожен з них декларується та реєструється.

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

З іншого боку, конвертер є більш загальним, він призначений для будь-якого перетворення в системі, а не лише для конверсій, пов’язаних з інтерфейсом користувача (рядок до цільового типу). Наприклад, Spring Integration широко використовує перетворювач для перетворення корисного навантаження повідомлення в потрібний тип.

Я думаю, що для потоків, пов’язаних з інтерфейсом користувача, PropertyEditors все ще підходять, особливо для випадку, коли вам потрібно зробити щось на замовлення для певного командного властивості. Для інших випадків я б взяв рекомендацію з Spring reference і замість цього написати конвертер (наприклад, для перетворення з Long id в сутність, наприклад, як зразок).


5
Ще одна добра річ у тому, що перетворювачі без стану, в той час як редактори властивостей є стаціонарними і створюються багато разів і реалізуються з багатьма api-дзвінками, я не думаю, що це матиме великий вплив на продуктивність, але перетворювачі просто чистіші та простіші.
Борис Треухов

1
@Boris cleaner так, але не простіше, особливо для початківців: вам потрібно написати 2 класи перетворювача + додати кілька рядків у xml config або java config. Я говорю про весняну форму MVC, яка надсилає / показує, із загальними конверсіями (не лише суб'єктами).
Джером Далберт

16
  1. Для перетворення в / з String використовуйте формати ( замість перетворювачів org.springframework.format.Formatter ). У ньому є методи друку (...) та розбору (...) , тому вам потрібен лише один клас замість двох. Для їх реєстрації використовуйте FormattingConversionServiceFactoryBean , який замість ConversionServiceFactoryBean може реєструвати як перетворювачі, так і формати .
  2. Новий матеріал у програмі Formatter має кілька додаткових переваг:
    • Інтерфейс форматів постачає об'єкт Locale методами його друку (...) та розбору (...) , тому перетворення рядків може бути чутливим до локальної точки.
    • На додаток до попередньо зареєстрованих форматів, FormattingConversionServiceFactoryBean поставляється з парою зручних попередньо зареєстрованих AnnotationFormatterFactory об'єктів, які дозволяють задавати додаткові параметри форматування за допомогою анотації. Наприклад: @RequestParam@DateTimeFormat (pattern = "MM-dd-yy")LocalDate baseDate ... Створити власні класи AnnotationFormatterFactory не дуже складно, для простого прикладу див. Spring's 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 є "найвищою зброєю" Spring Conversion API, але впроваджуючись, як тільки він зможе обробити більшість конверсій сутності, заощаджуючи час розробника - це велике полегшення, коли ви просто вказуєте класи сутностей як параметри свого контролера і ніколи не думаєте про реалізацію новий перетворювач (за винятком, звичайно, нестандартних типів, звичайно).


Приємне рішення вирішити лише питання конверсії сутностей, дякую за фокус. На початку не просто, як вам належить написати ще один клас, але простий і економний у перспективі.
Джером Далберт

Btw такий перетворювач може бути реалізований для будь-яких типів, які дотримуються якогось загального контракту - інший приклад: якщо ваші перелічувачі реалізують якийсь загальний інтерфейс зворотного пошуку - тоді ви також зможете реалізувати загальний перетворювач (він буде подібний до stackoverflow.com / питання / 5178622 /… )
Борис Треухов

@JeromeDalbert Так, початківцю трохи важко робити якісь важкі ваги, але якщо у вас є команда розробників, це буде простіше) PS І вам буде нудно реєструвати одні й ті ж редактори властивостей кожного разу на примусовій формі все одно)
Борис Треухов

1

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

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

Вам потрібно буде зареєструвати їх обох окремо, але принаймні це зменшує кількість файлів, які потрібно змінити, якщо ви внесете якісь зміни.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.