Guide des conversions de type ressort

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 tels que String, 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. Convertisseurs intégrés

Nous commencerons par les convertisseurs prêts à l'emploi au printemps; Jetons un coup d'œil à la conversion de chaîne en entier :

@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 connecter automatiquement le ConversionService fourni par Spring et d'appeler la méthode convert () . Le premier argument est la valeur que nous voulons convertir et le deuxième argument est le type de cible vers lequel nous voulons convertir.

En dehors de cet exemple de chaîne en entier , il existe de nombreuses autres combinaisons disponibles pour nous.

3. Création d'un convertisseur personnalisé

Jetons un coup d'œil à un exemple de conversion d'une représentation String d'un Employee en une instance Employee .

Voici la classe Employé :

public class Employee { private long id; private double salary; // standard constructors, getters, setters }

La chaîne sera une paire séparée par des virgules représentant l' ID et le salaire. Par exemple, «1,50000,00».

Afin de créer notre convertisseur personnalisé , nous devons implémenter l' interface Converter et implémenter la méthode convert () :

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 fini. Nous devons également informer Spring de ce nouveau convertisseur en ajoutant le StringToEmployeeConverter au FormatterRegistry . Cela peut être fait en implémentant le WebMvcConfigurer et en remplaçant la méthode addFormatters () :

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToEmployeeConverter()); } }

Et c'est tout. Notre nouveau convertisseur est maintenant disponible pour ConversionService et nous pouvons l'utiliser de la même manière que n'importe quel autre convertisseur 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 à l'aide de ConversionService , Spring est également capable de convertir implicitement des valeurs directement dans les méthodes Controller 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 les Convertisseurs . 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 voir, le test imprimera tous les détails de la demande ainsi que la réponse. Voici l' objet Employee au format JSON qui est renvoyé dans le cadre de la réponse:

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

4. Création d'une ConverterFactory

Il est également possible de créer une ConverterFactory qui crée des Converter à la demande. Ceci est particulièrement utile pour créer des Convertisseurs pour Enums .

Jetons un coup d'œil à un Enum vraiment simple:

public enum Modes { ALPHA, BETA; }

Ensuite, créons une StringToEnumConverterFactory qui peut générer des Converter s pour convertir une chaîne en n'importe quel 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); } }

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

Une chose à noter ici est que bien que nous utilisions notre Enum Modes pour démontrer l'utilisation, nous n'avons mentionné l' Enum nulle part dans StringToEnumConverterFactory . Notre classe d'usine est suffisamment générique pour générer les Convertisseurs à la demande pour tout type Enum .

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

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

Maintenant, ConversionService est prêt à convertir des chaînes en enum s:

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

5. Création d'un GenericConverter

Un GenericConverter nous offre plus de flexibilité pour créer un convertisseur pour une utilisation plus générique au prix de perdre un certain type de sécurité.

Prenons un exemple de conversion d'un entier , d'un double ou d'une chaîne en une valeur BigDecimal.Nous n'avons pas besoin d'écrire trois convertisseurs pour cela. Un simple GenericConverter 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 un ensemble 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); } }

L'étape suivante consiste à remplacer la méthode convert () 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éthode convert () est aussi simple que possible. Cependant, le TypeDescriptor 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 ce convertisseur :

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

L'utilisation de ce convertisseur 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 est disponible sur GitHub.