web-dev-qa-db-fra.com

Conversion de type Spring MVC: PropertyEditor ou Converter?

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 aucune configuration XML.

Jusqu'à présent, j'ai utilisé 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 entre tous mes contrôleurs, je pourrais toujours ajouter lignes dans ma config xml .


Mais Spring 3.x a introduit une nouvelle manière de procéder, en utilisant Converters :

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

Donc, disons que je veux utiliser des convertisseurs parce que c'est "la dernière alternative". Je devrais créer 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 classes. Avantage: pas besoin de lancer grâce à la généricité.

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

Deuxième inconvénient: Je n'ai pas trouvé de moyen simple (annotations ou autres possibilités de programmation) de le faire dans un contrôleur: rien de tel que someSpringObject.registerCustomConverter(...);.

Les seules solutions que j'ai trouvées seraient fastidieuses, pas simples, et concerneraient uniquement la liaison 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 au printemps 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? Existe-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.

126
Jerome Dalbert

Avec tous ces inconvénients, pourquoi utiliser des convertisseurs? Est-ce que je manque quelque chose? Existe-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 détaillée PropertyEditor et Converter, comment chacun est déclaré et enregistré.

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

En revanche, le convertisseur est plus générique, il est destiné à N'IMPORTE QUELLE conversion dans le système, et pas seulement aux conversions liées à l'interface utilisateur (Chaîne vers le type de cible). Par exemple, Spring Integration utilise de manière intensive 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 convient toujours, en particulier dans le cas où vous devez créer quelque chose de personnalisé pour une propriété de commande spécifique. Pour les autres cas, je prendrais la recommandation de Spring reference et écrirais plutôt un convertisseur (par exemple, pour convertir un id long en une entité, par exemple, comme exemple).

54
Biju Kunjummen
  1. Pour les conversions vers/depuis une chaîne, utilisez des outils de formatage (implémentez ) org.springframework.format.Formatter ) à la place des convertisseurs. Il a print (...) et parse (. ..) méthodes, 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. Le nouveau logiciel Formatter présente quelques avantages supplémentaires:
    • L’interface de formatage fournit l’objet Locale dans ses empreintes (...) et parse (...) méthodes, votre conversion de chaîne peut donc être sensible aux paramètres régionaux
    • En plus des formateurs préenregistrés, FormattingConversionServiceFactoryBean est livré avec un couple d’utilisateurs pré-enregistrés très pratiques AnnotationFormatterFactory objets, qui vous permettent de spécifier des paramètres de formatage supplémentaires via des annotations. 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 Spring's NumberFormatAnnotationFormatterFactory pour un exemple simple. Je pense que cela élimine le besoin de formateurs/éditeurs spécifiques à un contrôleur. Utilisez un ConversionService pour tous les contrôleurs et personnalisez le formatage via des annotations.
  3. Je conviens que si vous avez encore besoin d'une conversion de chaîne spécifique au contrôleur, le moyen le plus simple consiste à utiliser l'éditeur de propriétés personnalisées. (J'ai essayé d'appeler ' binder.setConversionService (...) ' dans mon @ InitBinder , mais il échoue car l'objet liant est fourni avec le service de conversion "global" déjà défini (on dirait que les classes de conversion par contrôleur sont déconseillées au printemps 3).
15
Alexander

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

Par exemple, si vous utilisez JPA, ce convertisseur peut rechercher si la classe spécifiée a @Entity annotation et utilisez @Id champ annoté pour extraire des informations et effectuer la recherche automatiquement en utilisant la valeur de chaîne fournie comme identifiant 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 sa mise en œuvre une fois qu'il sera capable de traiter la plupart des conversions d'entités, permettant ainsi au développeur de gagner du temps. C'est un grand soulagement lorsque vous spécifiez simplement des classes d'entités contrôleur et ne pensez jamais à mettre en œuvre un nouveau convertisseur (sauf pour les types personnalisés et non-entités, bien sûr).

7
Boris Treukhov

Vous pouvez en quelque sorte contourner la nécessité 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 devez toujours enregistrer les deux séparément, mais au moins, cela réduit le nombre de fichiers à modifier si vous apportez des modifications.

1
ntm