Spring MVC-Typkonvertierung: PropertyEditor oder Konverter?

129

Ich suche nach der einfachsten und einfachsten Möglichkeit, Daten in Spring MVC zu binden und zu konvertieren. Wenn möglich, ohne eine XML-Konfiguration vorzunehmen.

Bisher habe ich PropertyEditors wie folgt verwendet:

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

}

und

...
public class MyController {

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

    ...

}

Es ist einfach: Beide Konvertierungen werden in derselben Klasse definiert, und die Bindung ist unkompliziert. Wenn ich eine allgemeine Bindung für alle meine Controller durchführen wollte, konnte ich meiner XML-Konfiguration immer noch 3 Zeilen hinzufügen .


Mit Spring 3.x wurde jedoch mithilfe von Konvertern eine neue Methode eingeführt :

Innerhalb eines Spring-Containers kann dieses System als Alternative zu PropertyEditors verwendet werden

Nehmen wir also an, ich möchte Konverter verwenden, weil dies "die neueste Alternative" ist. Ich müsste zwei Konverter erstellen :

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

}

Erster Nachteil: Ich muss zwei Klassen machen. Vorteil: Dank der Großzügigkeit muss nicht gegossen werden.

Wie binde ich dann einfach Daten an die Konverter?

Zweiter Nachteil: Ich habe keine einfache Möglichkeit (Anmerkungen oder andere programmatische Funktionen) gefunden, dies in einer Steuerung zu tun: nichts Vergleichbares someSpringObject.registerCustomConverter(...);.

Die einzigen Möglichkeiten, die ich gefunden habe, wären mühsam, nicht einfach und nur in Bezug auf die allgemeine Cross-Controller-Bindung:

  • XML-Konfiguration :

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

Warum sollten Sie bei all diesen Nachteilen Konverter verwenden? Vermisse ich etwas Gibt es andere Tricks, die mir nicht bekannt sind?

Ich bin versucht, PropertyEditors weiter zu verwenden ... Die Bindung ist viel einfacher und schneller.

Jerome Dalbert
quelle
Hinweis (Ich bin auch mit Spring 3.2.17 gestolpert): Wenn Sie <mvc: annotation-powered /> verwenden, müssen Sie tatsächlich auf diese ConversionService-Bean verweisen: <mvc: annotation-powered Conversion-Service = "convertService" />
Mauhiz
addFormatters (...) muss öffentlich sein. Auch seit 5.0 ist WebMvcConfigurerAdapter veraltet.
Paco Abato

Antworten:

55

Warum sollten Sie bei all diesen Nachteilen Konverter verwenden? Vermisse ich etwas Gibt es andere Tricks, die mir nicht bekannt sind?

Nein, ich denke, Sie haben sowohl PropertyEditor als auch Converter sehr ausführlich beschrieben, wie jeder deklariert und registriert wird.

In meinen Augen ist der Umfang von PropertyEditors begrenzt. Sie helfen dabei, Zeichenfolgen in einen Typ zu konvertieren. Diese Zeichenfolge stammt normalerweise von der Benutzeroberfläche. Daher ist die Registrierung eines PropertyEditors mithilfe von @InitBinder und WebDataBinder sinnvoll.

Der Konverter hingegen ist allgemeiner und für JEDE Konvertierung im System vorgesehen - nicht nur für Konvertierungen im Zusammenhang mit der Benutzeroberfläche (Zeichenfolge zum Zieltyp). Beispielsweise verwendet Spring Integration einen Konverter in großem Umfang, um eine Nachrichtennutzlast in einen gewünschten Typ zu konvertieren.

Ich denke, für UI-bezogene Flows sind PropertyEditors immer noch geeignet, insbesondere für den Fall, dass Sie für eine bestimmte Befehlseigenschaft etwas Benutzerdefiniertes tun müssen. In anderen Fällen würde ich die Empfehlung aus der Spring-Referenz übernehmen und stattdessen einen Konverter schreiben (z. B. um beispielsweise von einer Long-ID in eine Entität zu konvertieren).

Biju Kunjummen
quelle
5
Eine weitere gute Sache ist, dass Konverter zustandslos sind, während Eigenschaftseditoren viele Male erstellt und mit vielen API-Aufrufen implementiert wurden. Ich denke nicht, dass dies einen großen Einfluss auf die Leistung haben wird, aber Konverter sind einfach sauberer und einfacher.
Boris Treukhov
1
@ Boris Cleaner ja, aber nicht einfacher, besonders für Anfänger: Sie müssen 2 Konverterklassen schreiben + mehrere Zeilen in XML-Konfiguration oder Java-Konfiguration hinzufügen. Ich spreche über das Senden / Anzeigen von Spring MVC-Formularen mit allgemeinen Konvertierungen (nicht nur Entitäten).
Jerome Dalbert
16
  1. Verwenden Sie für die Konvertierung von zu / von Zeichenfolgen Formatierer (implementieren Sie org.springframework.format.Formatter ) anstelle von Konvertern. Es verfügt über die Methoden print (...) und parse (...) , sodass Sie nur eine Klasse anstelle von zwei benötigen. Verwenden Sie zum Registrieren FormattingConversionServiceFactoryBean , mit der anstelle von ConversionServiceFactoryBean sowohl Konverter als auch Formatierer registriert werden können .
  2. Das neue Formatter-Material bietet einige zusätzliche Vorteile:
    • Die Formatierungsschnittstelle stellt das Locale-Objekt in den Methoden print (...) und parse (...) bereit , sodass Ihre Zeichenfolgenkonvertierung für das Gebietsschema empfindlich sein kann
    • Zusätzlich zu den vorregistrierten Formatierern enthält FormattingConversionServiceFactoryBean einige praktische vorregistrierte AnnotationFormatterFactory- Objekte, mit denen Sie zusätzliche Formatierungsparameter über Annotation angeben können. Zum Beispiel: @RequestParam@DateTimeFormat (pattern = "MM-tt-jj")LocalDate baseDate ... Es ist nicht sehr schwierig, eigene AnnotationFormatterFactory- Klassen zu erstellen. Ein einfaches Beispiel finden Sie in Spring's NumberFormatAnnotationFormatterFactory . Ich denke, dies macht Controller-spezifische Formatierer / Editoren überflüssig. Verwenden Sie einen ConversionService für alle Controller und passen Sie die Formatierung über Anmerkungen an.
  3. Ich bin damit einverstanden, dass der einfachste Weg die Verwendung des benutzerdefinierten Eigenschafteneditors ist, wenn Sie noch eine Controller-spezifische Zeichenfolgenkonvertierung benötigen. (Ich habe versucht, ' binder.setConversionService (...) ' in meiner @ InitBinder- Methode aufzurufen , dies schlägt jedoch fehl, da für das Binder-Objekt der bereits festgelegte 'globale' Konvertierungsdienst enthalten ist Feder 3).
Alexander
quelle
7

Die einfachste (vorausgesetzt, Sie verwenden ein Persistenz-Framework), aber nicht die perfekte Möglichkeit besteht darin, einen generischen Entitätskonverter über eine ConditionalGenericConverterSchnittstelle zu implementieren , die Entitäten mithilfe ihrer Metadaten konvertiert.

Wenn Sie beispielsweise JPA verwenden, überprüft dieser Konverter möglicherweise, ob die angegebene Klasse @EntityAnmerkungen enthält, und verwendet ein mit @IdAnmerkungen versehenes Feld, um Informationen zu extrahieren und die Suche automatisch durchzuführen, wobei der angegebene String-Wert als ID für die Suche verwendet wird.

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

ConditionalGenericConverter ist eine "ultimative Waffe" der Spring-Konvertierungs-API, wird jedoch implementiert, sobald die meisten Entitätskonvertierungen verarbeitet werden können, was Entwicklerzeit spart. Dies ist eine große Erleichterung, wenn Sie nur Entitätsklassen als Parameter Ihres Controllers angeben und nie an die Implementierung denken ein neuer Konverter (außer natürlich für benutzerdefinierte und Nicht-Entitätstypen).

Boris Treukhov
quelle
Gute Lösung, um nur mit der Entitätskonvertierung umzugehen, danke für den Trick. Am Anfang nicht einfach, da Sie noch eine Klasse schreiben müssen, aber auf lange Sicht einfach und zeitsparend.
Jerome Dalbert
Übrigens kann ein solcher Konverter für alle Typen implementiert werden, die einem generischen Vertrag entsprechen - ein weiteres Beispiel: Wenn Ihre Enums eine gemeinsame Reverse-Lookup-Schnittstelle implementieren -, können Sie auch einen generischen Konverter implementieren (ähnlich wie stackoverflow.com) / Fragen / 5178622 /… )
Boris Treukhov
@JeromeDalbert ja , es ist ein wenig schwierig für Anfänger einiger schweren Gewicht Sachen zu tun, aber wenn Sie ein Team von Entwicklern haben wird es einfacher sein) PS Und es wird langweilig wird die gleiche Eigenschaft Editoren jedes Mal auf Formular verbindlich sowieso) registrieren
Boris Treukhov
1

Sie können die Notwendigkeit von zwei separaten Konverterklassen umgehen, indem Sie die beiden Konverter als statische innere Klassen implementieren.

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

Sie müssten beide weiterhin separat registrieren, aber dies verringert zumindest die Anzahl der Dateien, die Sie ändern müssen, wenn Sie Änderungen vornehmen.

ntm
quelle