Spring MVC type conversion: PropertyEditor czy Converter?

Szukam najprostszego i najprostszego sposobu wiązania i konwersji danych w Spring MVC. Jeśli to możliwe, bez robienia żadnej konfiguracji xml.

Do tej pory używałem PropertyEditors Tak:

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();
    }

}

I

...
public class MyController {

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

    ...

}

Jest to proste : obie konwersje są zdefiniowane w tej samej klasie, a wiązanie jest proste. Gdybym chciał zrobić ogólne powiązanie między wszystkimi kontrolerami, nadal mógłbym dodać 3 linie w moim xml config .


Ale Wiosna 3.x wprowadził do tego nowy sposób, używając konwerterów :

Wewnątrz pojemnika sprężynowego, system ten może być używany jako alternatywa do PropertyEditors

Powiedzmy więc, że chcę używać konwerterów, ponieważ jest to "najnowsza alternatywa". Musiałbym stworzyć dwa konwertery:

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();
    }

}

Pierwsza wada: muszę zrobić dwie klasy. Korzyści: nie trzeba rzucać dzięki generyczność.

Więc, jak po prostu dane wiążą Konwertery ?

Druga wada: nie znalazłem żadnego prostego sposobu (adnotacje lub inne udogodnienia programistyczne), aby zrobić to w kontrolerze : nie ma to jak someSpringObject.registerCustomConverter(...);.

Jedyne sposoby, jakie znalazłem, byłyby żmudne, nie proste i tylko o ogólnym powiązaniu między kontrolerami: {]}

  • XML config :

    <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 config (tylko wiosną 3.1 + ) :

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

Z tymi wszystkimi wadami, po co używać konwerterów ? Coś przeoczyłem ? Czy są inne sztuczki, których nie jestem świadomy ?

[[6]] kusi mnie, aby dalej korzystać z PropertyEditors... Wiązanie jest znacznie łatwiejsze i szybsze.
Author: Sanghyun Lee, 2012-09-22

4 answers

Z tymi wszystkimi wadami, po co używać konwerterów ? Czy brakuje mi coś ? Czy są inne sztuczki, których nie jestem świadomy ?

Nie, myślę, że bardzo wyczerpująco opisałeś zarówno PropertyEditor, jak i konwerter, jak każdy z nich jest deklarowany i rejestrowany.

Według mnie, PropertyEditors mają ograniczony zakres-pomagają konwertować String na typ, A ten ciąg zazwyczaj pochodzi z interfejsu użytkownika, a więc rejestrowanie Propertyeditora za pomocą @initbinder i używanie WebDataBinder ma sens.

Konwerter

Z drugiej strony jest bardziej ogólny, jest przeznaczony do dowolnej konwersji w systemie - nie tylko do konwersji związanych z interfejsem użytkownika (String to target type). W przypadku np. Integracja Spring wykorzystuje konwerter do konwersji ładunku wiadomości na żądany typ.

Myślę, że dla przepływów związanych z UI PropertyEditors są nadal odpowiednie szczególnie w przypadku, gdy trzeba zrobić coś niestandardowego dla konkretnej właściwości polecenia. W innych przypadkach wziąłby rekomendację od Spring reference i napisał konwerter zamiast (na przykład, aby przekonwertować z długiego id na encję, powiedzmy, jako próbkę).

 55
Author: Biju Kunjummen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-22 20:26:45
  1. for to / from String conversions use formatters (implementacja org.springframework.format.Formatter) zamiast konwerterów. Ma print(...) oraz parse(...) Metody, więc potrzebujesz tylko jednej klasy zamiast dwóch. Aby je zarejestrować, użyj FormattingConversionServiceFactorybean, które mogą rejestrować zarówno Konwertery jak i formatery, zamiast ConversionServiceFactoryBean .
  2. nowy program formatujący ma kilka dodatkowych korzyści:
    • Interfejs Formatera dostarcza obiekt Locale w jego print(...) i parse(...) metody, więc konwersja łańcuchów może być wrażliwa na locale
    • oprócz wstępnie zarejestrowanych formaterów, FormattingConversionServiceFactorybean jest wyposażony w kilka poręcznych, wstępnie zarejestrowanych AnnotationFormatterFactory obiektów, które pozwalają określić dodatkowe parametry formatowania za pomocą adnotacji. Na przykład: @RequestParam @DateTimeFormat (pattern = "MM-dd-yy") LocalDate baseDate ... Tworzenie własnych klas Adnotationformatterfactory nie jest zbyt trudne, zobacz Spring ' S NumberFormatAnnotationFormatterFactory na prosty przykład. Myślę, że eliminuje to potrzebę stosowania formaterów / edytorów specyficznych dla kontrolerów. Użyj jednego ConversionService dla wszystkich kontrolerów i dostosuj formatowanie za pomocą Przypisy
  3. zgadzam się, że jeśli nadal potrzebujesz konwersji ciągów znaków specyficznych dla kontrolera, najprostszym sposobem jest użycie niestandardowego edytora właściwości. (Próbowałem zadzwonić do Bindera.setConversionService(...) 'w mojej metodzie @initbinder, ale nie powiodło się, ponieważ obiekt binder ma już ustawioną usługę konwersji' global'. Wygląda na to, że Klasy konwersji per-kontrolera są zniechęcane wiosną 3).
 16
Author: Alexander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-10 08:16:54

Najprostszym (zakładając, że używasz frameworka persistence), ale nie idealnym sposobem jest zaimplementowanie ogólnego konwertera encji za pomocą interfejsu ConditionalGenericConverter, który konwertuje encje za pomocą ich metadanych.

Na przykład, jeśli używasz JPA, ten konwerter może sprawdzić, czy podana klasa ma adnotację @Entity i użyć pola adnotacji @Id, aby wyodrębnić informacje i wykonać wyszukiwanie automatycznie przy użyciu dostarczonej wartości ciągu znaków jako Id dla wyszukiwania.

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

ConditionalGenericConverter jest "ultimate weapon" Z Spring convertion API, ale jest zaimplementowany raz będzie w stanie przetworzyć większość konwersji encji, oszczędzając czas dewelopera - to wielka ulga, gdy po prostu określić klasy encji jako parametry kontrolera i nigdy nie myśleć o implementacji nowego konwertera(z wyjątkiem niestandardowych i nie-encji typów, oczywiście).

 7
Author: Boris Treukhov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-22 15:14:53

Możesz obejść potrzebę posiadania dwóch oddzielnych klas konwerterów, implementując te dwa konwertery jako statyczne klasy wewnętrzne.

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

Nadal będziesz musiał zarejestrować oba pliki osobno, ale przynajmniej zmniejszy to liczbę plików, które musisz zmodyfikować, jeśli wprowadzisz jakiekolwiek zmiany.

 1
Author: ntm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-25 03:16:23