Conversione del tipo Spring MVC: PropertyEditor o Converter?


129

Sto cercando il modo più semplice e semplice per associare e convertire i dati in Spring MVC. Se possibile, senza eseguire alcuna configurazione XML.

Finora ho usato PropertyEditor in questo modo:

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

}

e

...
public class MyController {

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

    ...

}

È semplice: entrambe le conversioni sono definite nella stessa classe e l'associazione è semplice. Se volessi fare un binding generale su tutti i miei controller, potrei ancora aggiungere 3 righe nella mia configurazione XML .


Ma Spring 3.x ha introdotto un nuovo modo di farlo, usando Convertitori :

All'interno di un contenitore Spring, questo sistema può essere utilizzato in alternativa a PropertyEditor

Quindi diciamo che voglio usare i convertitori perché è "l'ultima alternativa". Dovrei creare due convertitori:

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

}

Primo inconveniente: devo fare due lezioni. Vantaggio: non è necessario lanciare grazie alla genericità.

Quindi, come posso semplicemente associare i dati ai convertitori?

Secondo svantaggio: non ho trovato alcun modo semplice (annotazioni o altre strutture programmatiche) per farlo in un controller: niente di simile someSpringObject.registerCustomConverter(...);.

L'unico modo che ho trovato sarebbe noioso, non semplice, e solo sull'associazione cross-controller generale:

  • Configurazione 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>
    
  • Config Java ( solo in Spring 3.1+ ):

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

Con tutti questi inconvenienti, perché usare i convertitori? Mi sto perdendo qualcosa ? Ci sono altri trucchi di cui non sono a conoscenza?

Sono tentato di continuare a utilizzare PropertyEditor ... Il binding è molto più semplice e rapido.


Nota (anch'io sono inciampato, usando Spring 3.2.17): quando si utilizza <mvc: annotation-driven /> è necessario fare effettivamente riferimento a questa conversioneService bean: <mvc: annotation-driven conversion-service = "conversionService" />
Mauhiz,

addFormatters (...) deve essere pubblico. Anche dalla 5.0 WebMvcConfigurerAdapter è obsoleto.
Paco Abato,

Risposte:


55

Con tutti questi inconvenienti, perché usare i convertitori? Mi sto perdendo qualcosa ? Ci sono altri trucchi di cui non sono a conoscenza?

No, penso che tu abbia descritto in modo esauriente sia PropertyEditor che Converter, come ciascuno sia dichiarato e registrato.

A mio avviso, i PropertyEditor hanno un ambito limitato: aiutano a convertire String in un tipo, e questa stringa in genere proviene dall'interfaccia utente, quindi ha senso registrare un PropertyEditor usando @InitBinder e usando WebDataBinder.

D'altro canto, il convertitore è più generico, è destinato a QUALSIASI conversione nel sistema, non solo per le conversioni correlate all'interfaccia utente (da tipo stringa a target). Ad esempio, Spring Integration utilizza ampiamente un convertitore per convertire un payload di messaggi nel tipo desiderato.

Penso che per i flussi relativi all'interfaccia utente i PropertyEditor siano ancora appropriati soprattutto per il caso in cui è necessario fare qualcosa di personalizzato per una specifica proprietà di comando. Per altri casi, vorrei prendere la raccomandazione dal riferimento Spring e scrivere invece un convertitore (ad esempio, per convertire da un ID lungo in un'entità, ad esempio, come esempio).


5
Un'altra cosa positiva del fatto che i convertitori sono apolidi, mentre gli editor di proprietà sono stateful e creati molte volte e implementati con molte chiamate API, non penso che ciò avrà un impatto importante sulle prestazioni, ma i convertitori sono semplicemente più puliti e più semplici.
Boris Treukhov,

1
@Boris Cleaner sì, ma non più semplice, soprattutto per un principiante: devi scrivere 2 classi di convertitori + aggiungere diverse righe di in xml config o java config. Sto parlando di invio / visualizzazione del modulo Spring MVC, con conversioni generali (non solo entità).
Jerome Dalbert,

16
  1. Per le conversioni da / a stringa utilizzare i formattatori (implementare org.springframework.format.Formatter ) anziché i convertitori. Ha metodi di stampa (...) e analisi (...) , quindi è necessaria solo una classe anziché due. Per registrarli, utilizzare FormattingConversionServiceFactoryBean , che può registrare sia i convertitori che i formatter, anziché ConversionServiceFactoryBean .
  2. Il nuovo materiale Formatter ha un paio di vantaggi aggiuntivi:
    • L'interfaccia del formatter fornisce l'oggetto Locale nei suoi metodi print (...) e parse (...) , quindi la conversione della stringa può essere sensibile alle impostazioni locali
    • Oltre ai formatter preregistrati, FormattingConversionServiceFactoryBean viene fornito con un paio di pratici oggetti AnnotationFormatterFactory preregistrati , che consentono di specificare parametri di formattazione aggiuntivi tramite annotazione. Ad esempio: @RequestParam@DateTimeFormat (modello = "MM-GG-AA")LocalDate baseDate ... Non è molto difficile creare le proprie classi AnnotationFormatterFactory , vedere NumberFormatAnnotationFormatterFactory di Spring per un semplice esempio. Penso che questo elimini la necessità di formattatori / editor specifici del controller. Utilizza un servizio di conversione per tutti i controller e personalizza la formattazione tramite annotazioni.
  3. Sono d'accordo che se hai ancora bisogno di una conversione di stringa specifica del controller, il modo più semplice è ancora utilizzare l'editor di proprietà personalizzato. (Ho provato a chiamare " binder.setConversionService (...) " nel mio metodo @InitBinder , ma fallisce, poiché l'oggetto binder viene fornito con il servizio di conversione "globale" già impostato. Sembra che le classi di conversione per controller siano scoraggiate in Primavera 3).

7

Il più semplice (supponendo che si stia utilizzando un framework di persistenza), ma non il modo perfetto è implementare un convertitore di entità generico tramite ConditionalGenericConverterinterfaccia che convertirà le entità usando i loro metadati.

Ad esempio, se si utilizza JPA, questo convertitore potrebbe verificare se la classe specificata ha @Entityannotazioni e utilizzare il @Idcampo annotato per estrarre informazioni ed eseguire la ricerca automaticamente utilizzando il valore String fornito come ID per la ricerca.

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

ConditionalGenericConverter è "un'arma fondamentale" dell'API di conversione di Spring, ma implementata una volta che sarà in grado di elaborare la maggior parte delle conversioni di entità, risparmiando tempo per gli sviluppatori: è un grande sollievo quando si specificano solo le classi di entità come parametri del proprio controller e non si pensa mai all'implementazione un nuovo convertitore (ad eccezione dei tipi personalizzati e non entità, ovviamente).


Bella soluzione per gestire solo la conversione di entità, grazie per il trucco. All'inizio non è semplice perché devi scrivere un'altra classe, ma a lungo termine semplice e veloce.
Jerome Dalbert,

Tuttavia, un tale convertitore può essere implementato per tutti i tipi che aderiscono a un contratto generico - un altro esempio: se i tuoi enum implementano un'interfaccia di ricerca inversa comune - allora sarai anche in grado di implementare un convertitore generico (sarà simile a stackoverflow.com / domande / 5178622 /… )
Boris Treukhov,

@JeromeDalbert sì, è un po 'difficile per un principiante fare cose pesanti, ma se hai un team di sviluppatori sarà più semplice) PS E diventerà noioso registrare gli stessi editor di proprietà ogni volta sul form binding)
Boris Treukhov,

1

È possibile aggirare la necessità di disporre di due classi Converter separate implementando i due Convertitori come classi interne statiche.

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

Dovresti comunque registrarli entrambi separatamente, ma almeno questo riduce il numero di file che devi modificare se apporti delle modifiche.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.