Guia para conversões de tipo de mola
1. Introdução
Neste artigo, daremos uma olhada nas conversões de tipo do Spring.
O Spring fornece vários conversores prontos para uso para tipos integrados; isso significa converter de / para tipos básicos comoString, Integer, Booleane vários outros tipos.
Além disso, o Spring também fornece um SPI de conversão de tipo sólido para o desenvolvimento de nossos conversores personalizados.
2. Converters integrado
Começaremos com os conversores prontos para uso no Spring; vamos dar uma olhada na conversão deString emInteger:
@Autowired
ConversionService conversionService;
@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
assertThat(
conversionService.convert("25", Integer.class)).isEqualTo(25);
}
A única coisa que precisamos fazer aqui é ligar automaticamente oConversionService fornecido pelo Spring e chamar o métodoconvert(). O primeiro argumento é o valor que queremos converter e o segundo argumento é o tipo de destino para o qual queremos converter.
Além deste exemplo deString aInteger, existem várias outras combinações disponíveis para nós.
3. Criando umConverter personalizado
Vamos dar uma olhada em um exemplo de conversão de uma representaçãoString de uma instânciaEmployee em uma instânciaEmployee.
Aqui está a classeEmployee:
public class Employee {
private long id;
private double salary;
// standard constructors, getters, setters
}
OString será um par separado por vírgulas que representaidesalary.. Por exemplo, “1,50000,00”.
Para criar nossoConverter personalizado, precisamos implementar a interfaceConverter<S, T> e implementar o métodoconvert():
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]));
}
}
Ainda não terminamos. Também precisamos contar ao Spring sobre esse novo conversor adicionandoStringToEmployeeConverter aFormatterRegistry. Isso pode ser feito implementando o métodoWebMvcConfigurere substituindo o métodoaddFormatters():
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
}
}
E é isso. Nosso novoConverter agora está disponível paraConversionService e podemos usá-lo da mesma maneira que qualquer outroConverter 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. Conversão implícita
Além dessas conversões explícitas usandoConversionService,Spring is also capable of implicitly converting values right in Controller methods para todos os conversores registrados:
@RestController
public class StringToEmployeeConverterController {
@GetMapping("/string-to-employee")
public ResponseEntity
Esta é uma forma mais natural de usarConverters. Vamos adicionar um teste para vê-lo em ação:
@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 você pode ver, o teste imprimirá todos os detalhes da solicitação, bem como a resposta. Aqui está o objetoEmployee no formato JSON que é retornado como parte da resposta:
{"id":1,"salary":2000.0}
4. Criando umConverterFactory
Também é possível criar umConverterFactory que criaConverters sob demanda. Isso é particularmente útil na criação deConverters paraEnums.
Vamos dar uma olhada em um Enum realmente simples:
public enum Modes {
ALPHA, BETA;
}
A seguir, vamos criar umStringToEnumConverterFactory que pode gerarConverters para converter umString em qualquerEnum:
@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, a classe fábrica usa internamente uma implementação da interfaceConverter.
Uma coisa a notar aqui é que, embora usemos nossoModes Enum para demonstrar o uso, não mencionamos oEnum em nenhum lugar doStringToEnumConverterFactory. Our factory class is generic enough to generate the Converters on demand for any Enum type.
A próxima etapa é registrar esta classe de fábrica conforme registramos nossoConverter no exemplo anterior:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
Agora oConversionService está pronto para converterStrings emEnums:
@Test
public void whenConvertStringToEnum_thenSuccess() {
assertThat(conversionService.convert("ALPHA", Modes.class))
.isEqualTo(Modes.ALPHA);
}
5. Criando umGenericConverter
UmGenericConverter nos fornece mais flexibilidade para criar umConverter para um uso mais genérico ao custo de perder algum tipo de segurança.
Vamos considerar um exemplo de conversão deInteger,Double ouString em um valorBigDecimal. Não precisamos escrever trêsConverters para isso . Um simplesGenericConverter pode servir ao propósito.
O primeiro passo é informar ao Spring quais tipos de conversão são suportados. Fazemos isso criando umSet deConvertiblePair:
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);
}
}
A próxima etapa é substituir o métodoconvert() na mesma classe:
@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);
}
}
O métodoconvert() é o mais simples possível. No entanto, oTypeDescriptor nos fornece grande flexibilidade em termos de obter os detalhes relativos à origem e ao tipo de destino.
Como você já deve ter adivinhado, a próxima etapa é registrar esteConverter:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
registry.addConverter(new GenericBigDecimalConverter());
}
Usar esteConverter é semelhante aos outros exemplos que já vimos:
@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. Conclusão
Neste tutorial, vimos como usar e estender o sistema de conversão de tipo do Spring com vários exemplos.
Como sempre, o código-fonte completo deste artigo pode ser encontradoover on GitHub.