Conversion de type Spring MVC: PropertyEditor ou Converter?

129

Je recherche le moyen le plus simple et le plus simple de lier et de convertir des données dans Spring MVC. Si possible, sans faire de configuration xml.

Jusqu'à présent, j'utilise PropertyEditors comme ceci:

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

}

et

...
public class MyController {

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

    ...

}

C'est simple: les deux conversions sont définies dans la même classe et la liaison est simple. Si je voulais faire une liaison générale sur tous mes contrôleurs, je pourrais toujours ajouter 3 lignes dans ma configuration xml .


Mais Spring 3.x a introduit une nouvelle façon de le faire, en utilisant des convertisseurs :

Dans un conteneur Spring, ce système peut être utilisé comme alternative aux PropertyEditors

Alors disons que je veux utiliser des convertisseurs parce que c'est "la dernière alternative". Il faudrait que je crée deux convertisseurs:

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

}

Premier inconvénient: je dois faire deux cours. Avantage: pas besoin de lancer grâce à la généricité.

Alors, comment puis-je simplement lier les données aux convertisseurs?

Deuxième inconvénient: je n'ai trouvé aucun moyen simple (annotations ou autres facilités de programmation) de le faire dans un contrôleur: rien de telsomeSpringObject.registerCustomConverter(...); .

Les seuls moyens que j'ai trouvés seraient fastidieux, pas simples et ne concerneraient que la liaison générale entre contrôleurs:

  • Configuration 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>
  • Configuration Java ( uniquement dans Spring 3.1+ ):

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

Avec tous ces inconvénients, pourquoi utiliser des convertisseurs? Est-ce que je manque quelque chose? Y a-t-il d'autres astuces dont je ne suis pas au courant?

Je suis tenté de continuer à utiliser PropertyEditors ... La liaison est beaucoup plus simple et rapide.

Jérôme Dalbert
la source
Remarque (j'ai trébuché aussi, en utilisant Spring 3.2.17): lors de l'utilisation de <mvc: annotation-driven />, il est nécessaire de faire référence à ce bean conversionService: <mvc: annotation-driven conversion-service = "conversionService" />
mauhiz
addFormatters (...) doit être public. De plus, depuis la version 5.0, WebMvcConfigurerAdapter est obsolète.
Paco Abato

Réponses:

55

Avec tous ces inconvénients, pourquoi utiliser des convertisseurs? Est-ce que je manque quelque chose? Y a-t-il d'autres astuces dont je ne suis pas au courant?

Non, je pense que vous avez décrit de manière très complète PropertyEditor et Converter, comment chacun est déclaré et enregistré.

Dans mon esprit, PropertyEditors a une portée limitée - ils aident à convertir String en type, et cette chaîne provient généralement de l'interface utilisateur, et donc enregistrer un PropertyEditor à l'aide de @InitBinder et de WebDataBinder a du sens.

Le convertisseur, quant à lui, est plus générique, il est destiné à TOUTE conversion dans le système - pas seulement pour les conversions liées à l'interface utilisateur (chaîne en type cible). Par exemple, Spring Integration utilise un convertisseur pour convertir une charge utile de message en un type souhaité.

Je pense que pour les flux liés à l'interface utilisateur, PropertyEditors est toujours approprié, en particulier dans le cas où vous devez faire quelque chose de personnalisé pour une propriété de commande spécifique. Pour les autres cas, je prendrais la recommandation de la référence Spring et j'écrirais un convertisseur à la place (par exemple, pour convertir un identifiant long en une entité, par exemple, comme un échantillon).

Biju Kunjummen
la source
5
Une autre bonne chose que les convertisseurs sont sans état, alors que les éditeurs de propriétés sont avec état et créés plusieurs fois et implémentés avec de nombreux appels API, je ne pense pas que cela aura un impact majeur sur les performances, mais les convertisseurs sont simplement plus propres et plus simples.
Boris Treukhov
1
@Boris Cleaner oui, mais pas plus simple, surtout pour un débutant: il faut écrire 2 classes de convertisseur + ajouter plusieurs lignes de in xml config ou java config. Je parle de soumission / affichage de formulaire Spring MVC, avec des conversions générales (pas seulement des entités).
Jerome Dalbert
16
  1. Pour les conversions de chaîne vers / depuis, utilisez des formateurs (implémentez org.springframework.format.Formatter ) au lieu de convertisseurs. Il a des méthodes print (...) et parse (...) , vous n'avez donc besoin que d'une classe au lieu de deux. Pour les enregistrer, utilisez FormattingConversionServiceFactoryBean , qui peut enregistrer à la fois les convertisseurs et les formateurs, au lieu de ConversionServiceFactoryBean .
  2. Les nouveaux éléments du formateur présentent quelques avantages supplémentaires:
    • L'interface du formateur fournit l'objet Locale dans ses méthodes print (...) et parse (...) , de sorte que votre conversion de chaîne peut être sensible aux paramètres régionaux
    • En plus des formateurs pré-enregistrés, FormattingConversionServiceFactoryBean est livré avec quelques objets AnnotationFormatterFactory préenregistrés pratiques , qui vous permettent de spécifier des paramètres de mise en forme supplémentaires via l'annotation. Par exemple: @RequestParam@DateTimeFormat (modèle = "MM-jj-aa")LocalDate baseDate ... Il n'est pas très difficile de créer vos propres classes AnnotationFormatterFactory , voir NumberFormatAnnotationFormatterFactory de Spring pour un exemple simple. Je pense que cela élimine le besoin de formateurs / éditeurs spécifiques au contrôleur. Utilisez un ConversionService pour tous les contrôleurs et personnalisez le formatage via des annotations.
  3. Je suis d'accord que si vous avez toujours besoin d'une conversion de chaîne spécifique au contrôleur, le moyen le plus simple est toujours d'utiliser l'éditeur de propriétés personnalisées. (J'ai essayé d'appeler ' binder.setConversionService (...) ' dans ma méthode @InitBinder , mais cela échoue, car l'objet binder est livré avec le service de conversion 'global' déjà défini. On dirait que les classes de conversion par contrôleur sont déconseillées dans Printemps 3).
Alexandre
la source
7

Le plus simple (en supposant que vous utilisez un framework de persistance), mais pas le moyen idéal, consiste à implémenter un convertisseur d'entité générique via ConditionalGenericConverter interface qui convertira les entités en utilisant leurs métadonnées.

Par exemple, si vous utilisez JPA, ce convertisseur peut rechercher si la classe spécifiée a une @Entityannotation et utiliser un @Idchamp annoté pour extraire des informations et effectuer la recherche automatiquement en utilisant la valeur String fournie comme Id pour la recherche.

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

ConditionalGenericConverter est une «arme ultime» de l'API de conversion Spring, mais mise en œuvre une fois qu'elle sera capable de traiter la plupart des conversions d'entités, ce qui fait gagner du temps aux développeurs - c'est un grand soulagement lorsque vous spécifiez simplement des classes d'entités comme paramètres de votre contrôleur et que vous ne pensez jamais à l'implémentation un nouveau convertisseur (sauf pour les types personnalisés et non-entité, bien sûr).

Boris Treukhov
la source
Belle solution pour traiter uniquement la conversion d'entité, merci pour l'astuce. Pas simple au début car vous devez écrire une classe de plus, mais simple et rapide à long terme.
Jerome Dalbert
Btw, un tel convertisseur peut être implémenté pour tous les types qui adhèrent à un contrat générique - un autre exemple: si vos énumérations implémentent une interface de recherche inversée commune - vous pourrez également implémenter un convertisseur générique (il sera similaire à stackoverflow.com / questions / 5178622 /… )
Boris Treukhov
@JeromeDalbert oui c'est un peu difficile pour un débutant de faire des trucs lourds, mais si vous avez une équipe de développeurs ce sera plus simple) PS Et ça deviendra ennuyeux d'enregistrer les mêmes éditeurs de propriétés à chaque fois sur la liaison de formulaire de toute façon)
Boris Treukhov
1

Vous pouvez en quelque sorte contourner le besoin d'avoir deux classes de convertisseur distinctes en implémentant les deux convertisseurs en tant que classes internes statiques.

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

Vous devrez toujours les enregistrer séparément, mais au moins cela réduit le nombre de fichiers que vous devez modifier si vous apportez des modifications.

ntm
la source