Guide pour les conversions de types de printemps

Guide des conversions de types de printemps

1. introduction

Dans cet article, nous examinerons les conversions de type de Spring.

Spring fournit divers convertisseurs prêts à l'emploi pour les types intégrés; cela signifie convertir vers / à partir de types de base commeString, Integer, Boolean et un certain nombre d'autres types.

En dehors de cela, Spring fournit également un SPI de conversion de type solide pour le développement de nos convertisseurs personnalisés.

2. Converters intégré

Nous commencerons par les convertisseurs prêts à l'emploi au printemps; Regardons la conversion deString enInteger:

@Autowired
ConversionService conversionService;

@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
    assertThat(
      conversionService.convert("25", Integer.class)).isEqualTo(25);
}

La seule chose que nous devons faire ici est de câbler automatiquement lesConversionService fournis par Spring et d'appeler la méthodeconvert(). Le premier argument est la valeur que nous voulons convertir et le second argument est le type de cible que nous voulons convertir.

En dehors de cet exemple deString àInteger, de nombreuses autres combinaisons sont disponibles pour nous.

3. Création d'unConverter personnalisé

Jetons un œil à un exemple de conversion d'une représentationString d'une instanceEmployee en une instanceEmployee.

Voici la classeEmployee:

public class Employee {

    private long id;
    private double salary;

    // standard constructors, getters, setters
}

LeString sera une paire séparée par des virgules représentantid etsalary. Par exemple, «1,50000.00».

Afin de créer nosConverter personnalisés, nous devons implémenter l'interfaceConverter<S, T> et implémenter la méthodeconvert():

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

Nous n’avons pas encore terminé. Nous devons également informer Spring de ce nouveau convertisseur en ajoutant lesStringToEmployeeConverter auxFormatterRegistry. Cela peut être fait en implémentant leWebMvcConfigurer et en remplaçant la méthodeaddFormatters():

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToEmployeeConverter());
    }
}

Et c'est tout. Notre nouveauConverter est maintenant disponible pour lesConversionService et nous pouvons l'utiliser de la même manière que n'importe quel autreConverter intégré:

@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. Conversion implicite

Au-delà de ces conversions explicites utilisant lesConversionService,Spring is also capable of implicitly converting values right in Controller methods pour tous les convertisseurs enregistrés:

@RestController
public class StringToEmployeeConverterController {

    @GetMapping("/string-to-employee")
    public ResponseEntity getStringToEmployee(
      @RequestParam("employee") Employee employee) {
        return ResponseEntity.ok(employee);
    }
}


C'est une manière plus naturelle d'utiliser lesConverters. Ajoutons un test pour le voir en action:

@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)))
}

Comme vous pouvez le constater, le test imprimera tous les détails de la demande ainsi que la réponse. Voici l'objetEmployee au format JSON qui est renvoyé dans le cadre de la réponse:

{"id":1,"salary":2000.0}

4. Créer unConverterFactory

Il est également possible de créer unConverterFactory qui crée desConverter à la demande. Ceci est particulièrement utile pour créerConverters pourEnums.

Jetons un œil à un Enum très simple:

public enum Modes {
    ALPHA, BETA;
}

Ensuite, créons unStringToEnumConverterFactory qui peut générer desConverter pour convertir unString en toutEnum:

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

Comme nous pouvons le voir, la classe factory utilise en interne une implémentation de l'interfaceConverter.

Une chose à noter ici est que bien que nous utilisions nosModes Enum pour démontrer l'utilisation, nous n'avons mentionné lesEnum nulle part dans lesStringToEnumConverterFactory. Our factory class is generic enough to generate the Converters on demand for any Enum type.

L'étape suivante consiste à enregistrer cette classe d'usine comme nous avons enregistré nosConverter dans l'exemple précédent:

@Override
public void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new StringToEmployeeConverter());
    registry.addConverterFactory(new StringToEnumConverterFactory());
}

Maintenant, leConversionService est prêt à convertirStrings enEnums:

@Test
public void whenConvertStringToEnum_thenSuccess() {
    assertThat(conversionService.convert("ALPHA", Modes.class))
      .isEqualTo(Modes.ALPHA);
}

5. Créer unGenericConverter

UnGenericConverter nous offre plus de flexibilité pour créer unConverter pour une utilisation plus générique au prix de perdre une sécurité de type.

Prenons un exemple de conversion d'unInteger,Double ou d'unString en une valeurBigDecimal.Nous n'avons pas besoin d'écrire troisConverters pour cela . Un simpleGenericConverter pourrait servir cet objectif.

La première étape consiste à indiquer à Spring quels types de conversion sont pris en charge. Nous faisons cela en créant unSet 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);
    }
}

L'étape suivante consiste à remplacer la méthodeconvert() dans la même 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);
    }
}

La méthodeconvert() est aussi simple que possible. Cependant, leTypeDescriptor nous offre une grande flexibilité pour obtenir les détails concernant la source et le type de cible.

Comme vous l'avez peut-être déjà deviné, l'étape suivante consiste à enregistrer cesConverter:

@Override
public void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new StringToEmployeeConverter());
    registry.addConverterFactory(new StringToEnumConverterFactory());
    registry.addConverter(new GenericBigDecimalConverter());
}

L'utilisation de ceConverter est similaire aux autres exemples que nous avons déjà vus:

@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. Conclusion

Dans ce didacticiel, nous avons vu comment utiliser et étendre le système de conversion de type de Spring avec divers exemples.

Comme toujours, le code source complet de cet article peut être trouvéover on GitHub.