Spring MVC konwersja typu: PropertyEditor czy Converter?

129

Szukam najłatwiejszego i najprostszego sposobu na powiązanie i konwersję danych w Spring MVC. Jeśli to możliwe, bez wykonywania jakiejkolwiek konfiguracji XML.

Do tej pory korzystałem z PropertyEditors w następujący sposób:

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

    ...

}

To proste: obie konwersje są zdefiniowane w tej samej klasie, a powiązanie jest proste. Gdybym chciał wykonać ogólne powiązanie na wszystkich moich kontrolerach, nadal mógłbym dodać 3 wiersze w mojej konfiguracji xml .


Ale Spring 3.x wprowadził nowy sposób, aby to zrobić, używając konwerterów :

W kontenerze Spring ten system może być używany jako alternatywa dla PropertyEditors

Powiedzmy, ż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. Zaleta: nie ma potrzeby rzucania dzięki hojności.

Jak więc po prostu powiązać konwertery z danymi?

Druga wada: nie znalazłem prostego sposobu (adnotacje lub inne funkcje programistyczne), aby to zrobić w kontrolerze: nic podobnego someSpringObject.registerCustomConverter(...);.

Jedyne sposoby, które znalazłem, byłyby żmudne, nie proste i dotyczyłyby tylko ogólnego powiązania między kontrolerami:

  • Konfiguracja 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>
    
  • Konfiguracja Java ( tylko w Spring 3.1+ ):

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

Biorąc pod uwagę wszystkie te wady, po co używać konwerterów? Czy coś mi brakuje? Czy są inne sztuczki, których nie jestem świadomy?

Kusi mnie, aby dalej używać PropertyEditors ... Wiązanie jest znacznie łatwiejsze i szybsze.

Jerome Dalbert
źródło
Uwaga (ja też się natknąłem, używając Spring 3.2.17): podczas korzystania z <mvc: annotation-Based /> istnieje potrzeba odniesienia się do tego komponentu bean conversionService: <mvc: annotation-powered conversion-service = "conversionService" />
mauhiz
addFormatters (...) musi być publiczny. Również od wersji 5.0 WebMvcConfigurerAdapter jest przestarzały.
Paco Abato

Odpowiedzi:

55

Biorąc pod uwagę wszystkie te wady, po co używać konwerterów? Czy coś mi brakuje? Czy są inne sztuczki, których nie jestem świadomy?

Nie, myślę, że bardzo szczegółowo opisałeś zarówno PropertyEditor, jak i Converter, jak każdy z nich jest deklarowany i rejestrowany.

Moim zdaniem, PropertyEditors mają ograniczony zakres - pomagają przekonwertować String na typ, a ten ciąg zwykle pochodzi z interfejsu użytkownika, więc rejestracja PropertyEditor za pomocą @InitBinder i WebDataBinder ma sens.

Z drugiej strony konwerter jest bardziej ogólny, jest przeznaczony do KAŻDEJ konwersji w systemie - nie tylko do konwersji związanych z interfejsem użytkownika (ciąg na typ docelowy). Na przykład Spring Integration szeroko używa konwertera do konwersji ładunku wiadomości na żądany typ.

Myślę, że w przypadku przepływów związanych z interfejsem użytkownika PropertyEditors są nadal odpowiednie, szczególnie w przypadku, gdy trzeba zrobić coś niestandardowego dla określonej właściwości polecenia. W innych przypadkach skorzystałbym z zalecenia Springa i zamiast tego napisałbym konwerter (np. W celu konwersji z Long id na jednostkę, powiedzmy, jako próbkę).

Biju Kunjummen
źródło
5
Kolejną dobrą rzeczą jest to, że konwertery są bezstanowe, podczas gdy edytory właściwości są stanowe i tworzone wiele razy oraz implementowane z wieloma wywołaniami API, nie sądzę, aby miało to jakikolwiek znaczący wpływ na wydajność, ale konwertery są po prostu czystsze i prostsze.
Boris Treukhov,
1
@Boris cleaner tak, ale nie prostsze, szczególnie dla początkujących: musisz napisać 2 klasy konwertera + dodać kilka linii w konfiguracji xml lub konfiguracji java. Mowa o wysyłaniu / wyświetlaniu formularzy Spring MVC, z ogólnymi konwersjami (nie tylko byty).
Jerome Dalbert
16
  1. W przypadku konwersji do / z ciągów znaków zamiast konwerterów użyj elementów formatujących (zaimplementuj org.springframework.format.Formatter ). Ma metody print (...) i parse (...) , więc potrzebujesz tylko jednej klasy zamiast dwóch. Aby je zarejestrować, użyj FormattingConversionServiceFactoryBean , który może zarejestrować zarówno konwertery, jak i elementy formatujące, zamiast ConversionServiceFactoryBean .
  2. Nowa funkcja Formatter ma kilka dodatkowych zalet:
    • Interfejs programu formatującego dostarcza obiekt Locale w swoich metodach print (...) i parse (...) , więc konwersja ciągów może uwzględniać ustawienia regionalne
    • Oprócz wstępnie zarejestrowanych elementów formatujących, FormattingConversionServiceFactoryBean zawiera kilka przydatnych, wstępnie zarejestrowanych obiektów AnnotationFormatterFactory , które umożliwiają określenie dodatkowych parametrów formatowania za pośrednictwem adnotacji. Na przykład: @RequestParam@DateTimeFormat (wzorzec = "MM-dd-rr")LocalDate baseDate ... Tworzenie własnych klas AnnotationFormatterFactory nie jest trudne. Prosty przykład można znaleźć w witrynie Spring's NumberFormatAnnotationFormatterFactory . Myślę, że eliminuje to potrzebę stosowania elementów formatujących / edytorów specyficznych dla kontrolera. Użyj jednej usługi ConversionService dla wszystkich kontrolerów i dostosuj formatowanie za pomocą adnotacji.
  3. Zgadzam się, że jeśli nadal potrzebujesz konwersji ciągów specyficznych dla kontrolera, najprostszym sposobem jest nadal użycie edytora właściwości niestandardowych. (Próbowałem wywołać „ binder.setConversionService (...) ” w mojej metodzie @InitBinder , ale kończy się to niepowodzeniem, ponieważ obiekt binder zawiera już ustawioną usługę konwersji „globalnej”. Wygląda na to, że klasy konwersji na kontroler nie są zalecane w Wiosna 3).
Aleksandra
źródło
7

Najprostszym (zakładając, że używasz struktury trwałości), ale nie idealnym sposobem jest zaimplementowanie ogólnego konwertera jednostek za pośrednictwem ConditionalGenericConverterinterfejsu, który skonwertuje jednostki przy użyciu ich metadanych.

Na przykład, jeśli używasz JPA, ten konwerter może sprawdzić, czy określona klasa ma @Entityadnotację, i użyć @Idpola z adnotacją, aby wyodrębnić informacje i przeprowadzić wyszukiwanie automatycznie, używając podanej wartości String jako identyfikatora do wyszukiwania.

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

ConditionalGenericConverter jest "ostateczną bronią" interfejsu Spring Convertion API, ale zostanie wdrożona, gdy będzie w stanie przetworzyć większość konwersji jednostek, oszczędzając czas programisty - to wielka ulga, gdy po prostu określisz klasy jednostek jako parametry kontrolera i nigdy nie myśl o implementacji nowy konwerter (oczywiście z wyjątkiem typów niestandardowych i innych niż encje).

Boris Treukhov
źródło
Ładne rozwiązanie do radzenia sobie tylko z konwersją jednostek, dzięki za sztuczkę. Nie jest to łatwe na początku, ponieważ musisz napisać jeszcze jedną lekcję, ale na dłuższą metę proste i wymagające czasu.
Jerome Dalbert
Tak przy okazji, taki konwerter można zaimplementować dla dowolnych typów, które są zgodne z jakąś generyczną umową - inny przykład: jeśli twoje wyliczenia implementują jakiś wspólny interfejs wyszukiwania wstecznego - wtedy również będziesz mógł zaimplementować ogólny konwerter (będzie podobny do stackoverflow.com / pytania / 5178622 /… )
Boris Treukhov
@JeromeDalbert tak, to trochę trudne dla początkującego, aby zrobić coś ciężkiego, ale jeśli masz zespół programistów, będzie to prostsze) PS I nudne będzie rejestrowanie tych samych redaktorów nieruchomości za każdym razem przy wiązaniu formularzy)
Boris Treukhov
1

Możesz obejść potrzebę posiadania dwóch oddzielnych klas Converter, implementując dwie Converters 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 musiałbyś zarejestrować oba z nich osobno, ale przynajmniej zmniejsza to liczbę plików, które musisz zmodyfikować, jeśli wprowadzisz jakiekolwiek zmiany.

ntm
źródło