Conversión de tipo Spring MVC: PropertyEditor o Converter?

129

Estoy buscando la forma más fácil y sencilla de vincular y convertir datos en Spring MVC. Si es posible, sin hacer ninguna configuración xml.

Hasta ahora he estado usando PropertyEditors así:

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

}

y

...
public class MyController {

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

    ...

}

Es simple: ambas conversiones se definen en la misma clase y el enlace es sencillo. Si quisiera hacer un enlace general en todos mis controladores, aún podría agregar 3 líneas en mi configuración xml .


Pero Spring 3.x introdujo una nueva forma de hacerlo, utilizando convertidores :

Dentro de un contenedor Spring, este sistema se puede usar como una alternativa a PropertyEditors

Entonces, digamos que quiero usar Convertidores porque es "la última alternativa". Tendría que crear dos convertidores:

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

}

Primer inconveniente: tengo que hacer dos clases. Beneficio: no hay necesidad de lanzar gracias a su carácter genérico.

Entonces, ¿cómo simplemente enlazo los convertidores a los datos?

Segundo inconveniente: no he encontrado ninguna forma simple (anotaciones u otras instalaciones programáticas) para hacerlo en un controlador: nada parecido someSpringObject.registerCustomConverter(...);.

Las únicas formas que he encontrado serían tediosas, no simples y solo sobre el enlace general de controlador cruzado:

  • Configuración 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>
  • Configuración de Java ( solo en 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 todos estos inconvenientes, ¿por qué usar convertidores? Me estoy perdiendo de algo ? ¿Hay otros trucos que no conozco?

Estoy tentado a seguir usando PropertyEditors ... El enlace es mucho más fácil y rápido.

Jerome Dalbert
fuente
Nota (también tropecé, usando Spring 3.2.17): cuando se usa <mvc: annotation-driven /> es necesario referirse realmente a este bean conversionService: <mvc: annotation-driven conversion-service = "conversionService" />
mauhiz
addFormatters (...) debe ser público. También desde 5.0 WebMvcConfigurerAdapter está en desuso.
Paco Abato

Respuestas:

55

Con todos estos inconvenientes, ¿por qué usar convertidores? Me estoy perdiendo de algo ? ¿Hay otros trucos que no conozco?

No, creo que ha descrito de manera muy exhaustiva tanto PropertyEditor como Converter, cómo se declara y registra cada uno.

En mi opinión, los PropertyEditors tienen un alcance limitado: ayudan a convertir String en un tipo, y esta cadena generalmente proviene de la interfaz de usuario, por lo que tiene sentido registrar un PropertyEditor usando @InitBinder y WebDataBinder.

El convertidor, por otro lado, es más genérico, está destinado a CUALQUIER conversión en el sistema, no solo para las conversiones relacionadas con la interfaz de usuario (String to target type). Por ejemplo, Spring Integration usa un convertidor ampliamente para convertir una carga útil de mensajes al tipo deseado.

Creo que para los flujos relacionados con la interfaz de usuario, los PropertyEditors siguen siendo apropiados, especialmente para el caso en el que necesita hacer algo personalizado para una propiedad de comando específica. Para otros casos, tomaría la recomendación de la referencia de Spring y escribiría un convertidor en su lugar (por ejemplo, para convertir de un ID largo a una entidad, por ejemplo, como muestra).

Biju Kunjummen
fuente
55
Otra cosa buena es que los convertidores no tienen estado, mientras que los editores de propiedades tienen estado y se crean muchas veces y se implementan con muchas llamadas de API, no creo que esto tenga un impacto importante en el rendimiento, pero los convertidores son más limpios y simples.
Boris Treukhov
1
@Boris Cleaner sí, pero no es más simple, especialmente para un principiante: debe escribir 2 clases de convertidor + agregar varias líneas en xml config o java config. Estoy hablando del envío / visualización del formulario Spring MVC, con conversiones generales (no solo entidades).
Jerome Dalbert
16
  1. Para las conversiones de cadenas hacia / desde, use formateadores (implemente org.springframework.format.Formatter ) en lugar de convertidores. Tiene métodos de impresión (...) y análisis (...) , por lo que solo necesita una clase en lugar de dos. Para registrarlos, use FormattingConversionServiceFactoryBean , que puede registrar convertidores y formateadores, en lugar de ConversionServiceFactoryBean .
  2. El nuevo material Formatter tiene un par de beneficios adicionales:
    • La interfaz del formateador proporciona el objeto Locale en sus métodos print (...) y parse (...) , por lo que su conversión de cadena puede ser sensible a la configuración regional
    • Además de los formateadores prerregistrados , FormattingConversionServiceFactoryBean viene con un par de útiles objetos AnnotationFormatterFactory prerregistrados , que le permiten especificar parámetros de formato adicionales a través de la anotación. Por ejemplo: @RequestParam@DateTimeFormat (patrón = "MM-dd-aa")LocalDate baseDate ... No es muy difícil crear sus propias clases AnnotationFormatterFactory , consulte Spring's NumberFormatAnnotationFormatterFactory para ver un ejemplo simple. Creo que esto elimina la necesidad de formateadores / editores específicos del controlador. Use un ConversionService para todos los controladores y personalice el formato mediante anotaciones.
  3. Estoy de acuerdo en que si todavía necesita alguna conversión de cadena específica del controlador, la forma más sencilla es utilizar un editor de propiedades personalizado. (Intenté llamar a ' binder.setConversionService (...) ' en mi método @InitBinder , pero falla, ya que el objeto binder viene con el servicio de conversión 'global' ya establecido. Parece que las clases de conversión por controlador no se recomiendan en Primavera 3).
Alejandro
fuente
7

Lo más simple (suponiendo que esté utilizando un marco de persistencia), pero no la forma perfecta es implementar un convertidor de entidad genérico a través de la ConditionalGenericConverterinterfaz que convertirá entidades utilizando sus metadatos.

Por ejemplo, si está utilizando JPA, este convertidor puede buscar si la clase especificada tiene una @Entityanotación, y usar un @Idcampo anotado para extraer información y realizar la búsqueda automáticamente usando el valor de cadena proporcionado como Id para la búsqueda.

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

ConditionalGenericConverter es un "arma definitiva" de la API de conversión de Spring, pero se implementa una vez que pueda procesar la mayoría de las conversiones de entidades, ahorrando tiempo al desarrollador; es un gran alivio cuando solo especifica las clases de entidad como parámetros de su controlador y nunca piensa en implementar un nuevo convertidor (a excepción de los tipos personalizados y no de entidad, por supuesto).

Boris Treukhov
fuente
Buena solución para tratar solo con la conversión de entidades, gracias por el truco. No es simple al principio ya que tienes que escribir una clase más, pero a la larga es simple y ahorra tiempo.
Jerome Dalbert
Por cierto, dicho convertidor se puede implementar para cualquier tipo que se adhiera a algún contrato genérico, otro ejemplo: si sus enumeraciones implementan una interfaz de búsqueda inversa común, entonces también podrá implementar un convertidor genérico (será similar a stackoverflow.com / preguntas / 5178622 / ... )
Boris Treukhov
@JeromeDalbert sí, es un poco difícil para un principiante para hacer algunas cosas de peso pesado, pero si usted tiene un equipo de desarrolladores será más sencillo) PS y se convertirá en aburrido para registrar los mismos editores de propiedades cada vez que sobre la unión de forma de todos modos)
Boris Treukhov
1

Puede solucionar la necesidad de tener dos clases de convertidor separadas implementando los dos convertidores como clases internas estáticas.

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

Aún necesitaría registrar ambos por separado, pero al menos esto reduce la cantidad de archivos que necesita modificar si realiza algún cambio.

ntm
fuente