1. Introducción
En este artículo, veremos las conversiones de tipos de Spring.
Spring proporciona varios convertidores listos para usar para tipos integrados; esto significa convertir a / desde tipos básicos como String, Integer, Boolean y varios otros tipos.
Aparte de esto, Spring también proporciona un SPI de conversión de tipo sólido para desarrollar nuestros convertidores personalizados.
2. Convertidor incorporado s
Comenzaremos con los convertidores disponibles listos para usar en Spring; echemos un vistazo a la conversión de cadena a entero :
@Autowired ConversionService conversionService; @Test public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() { assertThat( conversionService.convert("25", Integer.class)).isEqualTo(25); }
Lo único que tenemos que hacer aquí es conectar automáticamente el ConversionService proporcionado por Spring y llamar al método convert () . El primer argumento es el valor que queremos convertir y el segundo argumento es el tipo de destino al que queremos convertir.
Aparte de este ejemplo de String to Integer , hay muchas otras combinaciones disponibles para nosotros.
3. Creación de un convertidor personalizado
Echemos un vistazo a un ejemplo de conversión de una representación de cadena de un empleado en una instancia de empleado .
Aquí está la clase de empleado :
public class Employee { private long id; private double salary; // standard constructors, getters, setters }
La cadena será un par separado por comas que representa la identificación y el salario. Por ejemplo, "1,50000.00".
Con el fin de crear nuestra costumbre convertidor , necesitamos implementar el convertidor de interfaz e implementar el convert () método:
public class StringToEmployeeConverter implements Converter { @Override public Employee convert(String from) { String[] data = from.split(","); return new Employee( Long.parseLong(data[0]), Double.parseDouble(data[1])); } }
Aún no hemos terminado. También necesitamos informar a Spring sobre este nuevo convertidor agregando el StringToEmployeeConverter al FormatterRegistry . Esto se puede hacer implementando WebMvcConfigurer y anulando el método addFormatters () :
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToEmployeeConverter()); } }
Y eso es. Nuestro nuevo convertidor ahora está disponible para ConversionService y podemos usarlo de la misma manera que cualquier otro convertidor integrado :
@Test public void whenConvertStringToEmployee_thenSuccess() { Employee employee = conversionService .convert("1,50000.00", Employee.class); Employee actualEmployee = new Employee(1, 50000.00); assertThat(conversionService.convert("1,50000.00", Employee.class)) .isEqualToComparingFieldByField(actualEmployee); }
3.1. Conversión implícita
Más allá de estas conversiones explícitas usando ConversionService , Spring también es capaz de convertir implícitamente valores directamente en los métodos del controlador para todos los convertidores registrados:
@RestController public class StringToEmployeeConverterController { @GetMapping("/string-to-employee") public ResponseEntity getStringToEmployee( @RequestParam("employee") Employee employee) { return ResponseEntity.ok(employee); } }
Esta es una forma más natural de utilizar Converter s. Agreguemos una prueba para verlo en acción:
@Test public void getStringToEmployeeTest() throws Exception { mockMvc.perform(get("/string-to-employee?employee=1,2000")) .andDo(print()) .andExpect(jsonPath("$.id", is(1))) .andExpect(jsonPath("$.salary", is(2000.0))) }
Como puede ver, la prueba imprimirá todos los detalles de la solicitud, así como la respuesta. Aquí está el objeto Empleado en formato JSON que se devuelve como parte de la respuesta:
{"id":1,"salary":2000.0}
4. Creación de una ConverterFactory
También es posible crear una ConverterFactory que cree Converter s bajo demanda. Esto es particularmente útil para crear convertidores para Enums .
Echemos un vistazo a un Enum realmente simple:
public enum Modes { ALPHA, BETA; }
A continuación, creemos un StringToEnumConverterFactory que puede generar Converter s para convertir un String en cualquier Enum :
@Component public class StringToEnumConverterFactory implements ConverterFactory { private static class StringToEnumConverter implements Converter { private Class enumType; public StringToEnumConverter(Class enumType) { this.enumType = enumType; } public T convert(String source) { return (T) Enum.valueOf(this.enumType, source.trim()); } } @Override public Converter getConverter( Class targetType) { return new StringToEnumConverter(targetType); } }
Como podemos ver, la clase de fábrica utiliza internamente una implementación de la interfaz Converter .
Una cosa a tener en cuenta aquí es que, aunque usaremos nuestra enumeración de modos para demostrar el uso, no hemos mencionado la enumeración en ninguna parte de StringToEnumConverterFactory . Nuestra clase de fábrica es lo suficientemente genérica como para generar los convertidores a pedido para cualquier tipo de Enum .
El siguiente paso es registrar esta clase de fábrica como registramos nuestro convertidor en el ejemplo anterior:
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToEmployeeConverter()); registry.addConverterFactory(new StringToEnumConverterFactory()); }
Ahora ConversionService está listo para convertir String s en Enum s:
@Test public void whenConvertStringToEnum_thenSuccess() { assertThat(conversionService.convert("ALPHA", Modes.class)) .isEqualTo(Modes.ALPHA); }
5. Creación de un conversor genérico
Un convertidor genérico nos proporciona más flexibilidad para crear un convertidor para un uso más genérico a costa de perder algún tipo de seguridad.
Consideremos un ejemplo de conversión de un entero , un doble o una cadena en un valor BigDecimal. No necesitamos escribir tres convertidores para esto. Un simple GenericConverter podría cumplir este propósito.
El primer paso es decirle a Spring qué tipos de conversión son compatibles. Hacemos esto creando un conjunto de ConvertiblePair :
public class GenericBigDecimalConverter implements GenericConverter { @Override public Set getConvertibleTypes () { ConvertiblePair[] pairs = new ConvertiblePair[] { new ConvertiblePair(Number.class, BigDecimal.class), new ConvertiblePair(String.class, BigDecimal.class)}; return ImmutableSet.copyOf(pairs); } }
El siguiente paso es anular el método convert () en la misma clase:
@Override public Object convert (Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (sourceType.getType() == BigDecimal.class) { return source; } if(sourceType.getType() == String.class) { String number = (String) source; return new BigDecimal(number); } else { Number number = (Number) source; BigDecimal converted = new BigDecimal(number.doubleValue()); return converted.setScale(2, BigDecimal.ROUND_HALF_EVEN); } }
El método convert () es tan simple como puede ser. Sin embargo, TypeDescriptor nos proporciona una gran flexibilidad en términos de obtener los detalles relacionados con la fuente y el tipo de destino.
Como ya habrás adivinado, el siguiente paso es registrar este convertidor :
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToEmployeeConverter()); registry.addConverterFactory(new StringToEnumConverterFactory()); registry.addConverter(new GenericBigDecimalConverter()); }
El uso de este convertidor es similar a los otros ejemplos que ya hemos visto:
@Test public void whenConvertingToBigDecimalUsingGenericConverter_thenSuccess() { assertThat(conversionService .convert(Integer.valueOf(11), BigDecimal.class)) .isEqualTo(BigDecimal.valueOf(11.00) .setScale(2, BigDecimal.ROUND_HALF_EVEN)); assertThat(conversionService .convert(Double.valueOf(25.23), BigDecimal.class)) .isEqualByComparingTo(BigDecimal.valueOf(Double.valueOf(25.23))); assertThat(conversionService.convert("2.32", BigDecimal.class)) .isEqualTo(BigDecimal.valueOf(2.32)); }
6. Conclusión
En este tutorial, hemos visto cómo usar y extender el sistema de conversión de tipos de Spring con varios ejemplos.
Como siempre, el código fuente completo de este artículo se puede encontrar en GitHub.