Como se analizó en la sección anterior, core.convert es un sistema de conversión de tipos de propósito general. Proporciona una API ConversionService unificada, así como una interfaz SPI Converter fuertemente tipada para implementar la lógica de conversión de un tipo a otro. El contenedor Spring utiliza este sistema para vincular los valores de las propiedades del bean. Además, tanto Spring Expression Language (SpEL) como DataBinder utilizan este sistema para vincular valores de campo. Por ejemplo, si SpEL necesita convertir Short a Long para completar el intento expression.setValue(Object bean, Object value), el core.convert realiza esta conversión.

Ahora considere los requisitos de conversión de tipos en un entorno de cliente típico, como una aplicación web o de escritorio. En dichos entornos, la conversión de String generalmente se realiza para respaldar el proceso de devolución de datos del lado del cliente, y también de regreso a String para respaldar el proceso de representación de la vista. Además, a menudo se requiere la localización de los valores String. La interfaz SPI más general core.convert Converter no aborda dichos requisitos de formato directamente. Para abordarlos directamente, Spring 3 introdujo una conveniente interfaz SPI, Formatter, que proporciona una alternativa simple y sólida a la implementación de PropertyEditor para entornos de cliente.

En general, puede utilizar la interfaz SPI Converter si necesita implementar una lógica de conversión de tipos de propósito general, por ejemplo, para convertir entre java.util.Date y Long. Puede utilizar la interfaz SPI Formatter si está trabajando en un entorno de cliente (como una aplicación web) y necesita analizar y generar valores de campo localizados. ConversionService proporciona una API de conversión de tipo único para ambas interfaces SPI.

Interfaz SPI Formatter

Interfaz SPI Formatter para implementar la lógica de formato de campo es simple y está fuertemente tipado. La siguiente lista muestra la definición de la interfaz Formatter:

package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter se extiende desde el bloque de construcción Printer y Parser interfaces. El siguiente listado muestra las definiciones de estas dos interfaces:

public interface Printer<T> {
    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;
public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

Para crear su propio Formatter, implemente la interfaz Formatter que se mostró anteriormente. Parametrice T como el tipo de objeto que desea formatear, por ejemplo, java.util.Date. Implemente la operación print() para imprimir una instancia de T para mostrarla en la configuración regional del cliente. Implemente una operación parse() para analizar una instancia de T a partir de la representación formateada devuelta desde la configuración regional del cliente. Su Formatter debería generar excepciones ParseException o IllegalArgumentException cuando falla un intento de análisis. Asegúrese de que su implementación de Formatter sea segura para subprocesos.

Los subpaquetes format contienen múltiples implementaciones de Formatter para mayor comodidad. El paquete Number contiene NumberStyleFormatter, CurrencyStyleFormatter y PercentStyleFormatter para formatear objetos Number que utilice java.text.NumberFormat. El paquete datetime contiene un DateFormatter para formatear objetos java.util.Date con java.text.DateFormat.

El siguiente DateFormatter es un ejemplo de implementación de Formatter:

Java
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
    private String pattern;
    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }
    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }
    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }
    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
Kotlin
class DateFormatter(private val pattern: String) : Formatter<Date> {
    override fun print(date: Date, locale: Locale)
            = getDateFormat(locale).format(date)
    @Throws(ParseException::class)
    override fun parse(formatted: String, locale: Locale)
            = getDateFormat(locale).parse(formatted)
    protected fun getDateFormat(locale: Locale): DateFormat {
        val dateFormat = SimpleDateFormat(this.pattern, locale)
        dateFormat.isLenient = false
        return dateFormat
    }
}

El equipo de Spring agradece la participación de la comunidad en el desarrollo de Formatter. Para hacer sus sugerencias, consulte "GitHub Issues".

Formato basado en anotaciones

El formato de campo se puede configurar por campo o tipo de anotación. Para vincular una anotación a Formatter, implemente AnnotationFormatterFactory. La siguiente lista muestra la definición de la interfaz AnnotationFormatterFactory:

package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
    Set<Class<?>> getFieldTypes();
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
    Parser<?> getParser(A annotation, Class<?> fieldType);
}

Para crear una implementación:

  1. Parametrizar A como campos annotationType , con el que desea asociar la lógica de formato, por ejemplo, org.springframework.format.annotation.DateTimeFormat.

  2. Let getFieldTypes() devuelve los tipos de campo para los que se puede utilizar la anotación.

  3. Deje que getPrinter() devuelva Printer para imprimir el valor del campo anotado.

  4. Deje que getParser() devuelva un Parser para analizar el clientValue para los campos de campo anotados.

La siguiente implementación de ejemplo de AnnotationFormatterFactory vincula el @NumberFormat anotación al formateador para que pueda especificar un estilo o patrón de numeración:

Java
public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {
    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }
    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }
    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }
    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
Kotlin
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
    override fun getFieldTypes(): Set<Class<*>> {
        return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
    }
    override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }
    override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }
    private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
        return if (annotation.pattern.isNotEmpty()) {
            NumberStyleFormatter(annotation.pattern)
        } else {
            val style = annotation.style
            when {
                style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
                style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
                else -> NumberStyleFormatter()
            }
        }
    }
            }

Para forzar el formato, puede anotar campos con @NumberFormat, como se muestra en el siguiente ejemplo:

Java
public class MyModel {
    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}
Kotlin
class MyModel(
    @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)

API de anotación de formato

En el paquete org.springframework.format.annotation hay Portable API de anotación de formatos. Puede utilizar @NumberFormat para dar formato a campos Number como Double y Long, y @DateTimeFormat para formatear java.util.Date, java.util.Calendar, Long (para marcas de tiempo de milisegundos) y java.time de JSR-310.

El siguiente ejemplo formatea java.util.Date como una fecha ISO (aaaa-MM-dd) @DateTimeFormat se utiliza:

Java
public class MyModel {
    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}
Kotlin
class MyModel(
    @DateTimeFormat(iso=ISO.DATE) private val date: Date
)

Interfaz SPI FormatterRegistry

FormatterRegistry es una interfaz SPI para registrar formateadores y conversores. FormattingConversionService es una implementación de FormatterRegistry adecuada para la mayoría de los entornos. Puede configurar esta opción mediante programación o declaración como un Spring Bean, por ejemplo usando FormattingConversionServiceFactoryBean. Debido a que esta implementación también implementa ConversionService, puede configurarlo directamente para usarlo con DataBinder de Spring y Spring Expression Language (SpEL).

De la siguiente manera: El listado muestra la interfaz SPI FormatterRegistry:

package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
    void addPrinter(Printer printer);
    void addParser(Parser parser);
    void addFormatter(Formatter formatter);
    void addFormatterForFieldType(Class fieldType, Formatter formatter);
    void addFormatterForFieldType(Class fieldType, Printer printer, Parser parser);
    void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory);
}

Como se desprende del listado anterior, puede registrar formateadores por tipo de campo o por anotación.

Interfaz SPI FormatterRegistry le permite configurar reglas de formato de forma centralizada, en lugar de duplicar dicha configuración para todos los controladores. Por ejemplo, puede forzar que todos los campos de fecha tengan un formato determinado o que los campos con una determinada anotación tengan un formato determinado. Con un FormatterRegistry genérico, usted define estas reglas una vez y se aplican siempre que sea necesario formatear.

Interfaz SPI FormatterRegistrar

FormatterRegistrar es una interfaz SPI para registrar formateadores y convertidores a través de FormatterRegistry. El siguiente listado muestra la definición de su interfaz:

package org.springframework.format;
public interface FormatterRegistrar {
    void registerFormatters(FormatterRegistry registry);
}

FormatterRegistrar es útil cuando se registran múltiples convertidores y formateadores relacionados para una categoría específica de formato, como el formato de fecha. También puede resultar útil si el registro declarativo no es suficiente; por ejemplo, si el formateador necesita indexarse bajo un tipo de campo específico que no sea su propio <T>, o si una Impresora El par Printer está registrado /Parser. La siguiente sección proporciona más información sobre cómo registrar convertidores y formateadores.

Configurar el formato en Spring MVC

Configurar el formato global de fecha y hora

Campos de fecha y hora predeterminados , no anotados con @DateTimeFormat, se convierten a partir de cadenas utilizando el estilo DateFormat.SHORT. Si lo desea, puede cambiar esto definiendo su propio formato global.

Para hacer esto, asegúrese de que Spring no registre formateadores predeterminados. En su lugar, registre formateadores manualmente con:

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrar

  • org.springframework.format.datetime.DateFormatterRegistrar

Por ejemplo, la siguiente configuración de Java registra un formato global de la forma yyyyMMdd:

Java
@Configuration
public class AppConfig {
    @Bean
    public FormattingConversionService conversionService() {
        // Use DefaultFormattingConversionService, but don't register default values
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        // Make sure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
        // Register the JSR-310 date conversion in a specific global format
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
        // Register the date conversion in a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
        return conversionService;
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun conversionService(): FormattingConversionService {
        // Use DefaultFormattingConversionService, but don't register default values
        return DefaultFormattingConversionService(false).apply {
            // Make sure @NumberFormat is still supported
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
            // Register JSR-310 date conversion in a specific global format
            val registrar = DateTimeFormatterRegistrar ()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)
            // Register the date conversion in a specific global format
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters (this)
        }
    }
}

Si prefiere la configuración basada en XML, puede utilizar FormattingConversionServiceFactoryBean. Lo siguiente El ejemplo muestra cómo hacer esto:

<beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="registerDefaultFormatters" value="false" />
    <property name="formatters">
        <set>
            <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
        </set>
    </property>
    <property name="formatterRegistrars">
        <set>
            <bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
                <property name="dateFormatter">
                    <bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
                        <property name="pattern" value="yyyyMMdd"/>
                    </bean>
                </property>
            </bean>
        </set>
    </property>
        </bean>
        </beans>

Tenga en cuenta que hay factores adicionales a considerar al configurar formatos de fecha y hora en aplicaciones web.