Leitfaden für Spring-Typumwandlungen

Leitfaden für die Konvertierung von Federtypen

1. Einführung

In diesem Artikel sehen wir uns die Typkonvertierungen von Spring an.

Spring bietet sofort einsatzbereite verschiedene Konverter für eingebaute Typen. Dies bedeutet die Konvertierung in / von Basistypen wieString, Integer, Boolean und einer Reihe anderer Typen.

Abgesehen davon bietet Spring auch ein SPI für die Konvertierung fester Typen für die Entwicklung unserer benutzerdefinierten Konverter.

2. EingebauteConverters

Wir beginnen mit den im Frühjahr sofort erhältlichen Konvertern. Schauen wir uns die Konvertierung vonString inIntegeran:

@Autowired
ConversionService conversionService;

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

Das einzige, was wir hier tun müssen, ist, die von Spring bereitgestelltenConversionService automatisch zu verdrahten und dieconvert()-Methode aufzurufen. Das erste Argument ist der Wert, den wir konvertieren möchten, und das zweite Argument ist der Zieltyp, in den wir konvertieren möchten.

Abgesehen von diesem Beispiel vonString bisIntegertehen uns viele verschiedene andere Kombinationen zur Verfügung.

3. Erstellen eines benutzerdefiniertenConverter

Schauen wir uns ein Beispiel für die Konvertierung einerString-Darstellung einerEmployee in eineEmployee-Instanz an.

Hier ist die KlasseEmployee:

public class Employee {

    private long id;
    private double salary;

    // standard constructors, getters, setters
}

String ist ein durch Kommas getrenntes Paar, dasid undsalary. darstellt. Beispiel: "1.50000,00".

Um unsere benutzerdefiniertenConverter zu erstellen, müssen wir dieConverter<S, T>-Schnittstelle und dieconvert()-Methode implementieren:

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

Wir sind noch nicht fertig. Wir müssen Spring auch über diesen neuen Konverter informieren, indem wirStringToEmployeeConverter zuFormatterRegistry addieren. Dies kann durch Implementieren der MethodeWebMvcConfigurer und Überschreiben der MethodeaddFormatters() erfolgen:

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

Und das ist es. Unsere neuenConverter sind jetzt für dieConversionService verfügbar und wir können sie auf die gleiche Weise wie alle anderen integriertenConverter verwenden:

@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. Implizite Konvertierung

Jenseits dieser expliziten Konvertierung unter Verwendung desConversionService,Spring is also capable of implicitly converting values right in Controller methods für alle registrierten Konverter:

@RestController
public class StringToEmployeeConverterController {

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


Dies ist eine natürlichere Methode zur Verwendung vonConverters. Fügen wir einen Test hinzu, um ihn in Aktion zu sehen:

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

Wie Sie sehen, werden beim Test alle Details der Anforderung sowie die Antwort gedruckt. Hier ist dasEmployee-Objekt im JSON-Format, das als Teil der Antwort zurückgegeben wird:

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

4. ConverterFactory erstellen

Es ist auch möglich,ConverterFactory zu erstellen, die bei BedarfConverters erstellen. Dies ist besonders hilfreich beim Erstellen vonConverters fürEnums.

Schauen wir uns eine wirklich einfache Aufzählung an:

public enum Modes {
    ALPHA, BETA;
}

Als nächstes erstellen wir einStringToEnumConverterFactory, dasConverters generieren kann, um einString in ein beliebigesEnum umzuwandeln:

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

Wie wir sehen können, verwendet die Factory-Klasse intern eine Implementierung derConverter-Schnittstelle.

Eine Sache, die hier zu beachten ist, ist, dass wir, obwohl wir unsereModes Enum verwenden, um die Verwendung zu demonstrieren, dieEnum nirgendwo in denStringToEnumConverterFactory erwähnt haben. Our factory class is generic enough to generate the Converters on demand for any Enum type.

Der nächste Schritt besteht darin, diese Factory-Klasse so zu registrieren, wie wir unsereConverterim vorherigen Beispiel registriert haben:

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

Jetzt istConversionService bereit,Strings inEnums umzuwandeln:

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

5. GenericConverter erstellen

EinGenericConverter bietet uns mehr Flexibilität beim Erstellen einesConverter für eine allgemeinere Verwendung auf Kosten des Verlusts einer gewissen Typensicherheit.

Betrachten wir ein Beispiel für die Konvertierung vonInteger,Double oderString in einenBigDecimal-Wert. Wir müssen hierfür nicht dreiConverters schreiben . Ein einfachesGenericConverter könnte den Zweck erfüllen.

Der erste Schritt besteht darin, Spring mitzuteilen, welche Konvertierungstypen unterstützt werden. Dazu erstellen wirSet vonConvertiblePair:

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

Der nächste Schritt besteht darin, dieconvert()-Methode in derselben Klasse zu überschreiben:

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

Die Methodeconvert()ist so einfach wie möglich. DieTypeDescriptor bieten uns jedoch eine große Flexibilität hinsichtlich der Ermittlung der Details zu Quelle und Zieltyp.

Wie Sie vielleicht bereits erraten haben, besteht der nächste Schritt darin, dieseConverter zu registrieren:

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

Die Verwendung diesesConverter ähnelt den anderen Beispielen, die wir bereits gesehen haben:

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

In diesem Tutorial haben wir anhand verschiedener Beispiele gesehen, wie das Typkonvertierungssystem von Spring verwendet und erweitert wird.

Wie immer finden Sie den vollständigen Quellcode für diesen Artikel inover on GitHub.