Spring型変換の手引き

1前書き

この記事では、Springの型変換について説明します。

Springは組み込み型のためにすぐに使えるさまざまなコンバータを提供します。これは、 String、Integer、Boolean 、その他多数の基本型などの基本型との間の変換を意味します。

これとは別に、Springはカスタムコンバーターを開発するためのソリッドタイプ変換SPIも提供しています。

2組み込みの __Converter __s

Springですぐに利用可能なコンバータから始めましょう。 String から Integer への変換を見てみましょう。

@Autowired
ConversionService conversionService;

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

ここでやらなければならないことは、Springが提供する ConversionService を自動配線して convert() メソッドを呼び出すことです。最初の引数は変換したい値で、2番目の引数は変換したいターゲット型です。

この String から Integer の例以外にも、さまざまな組み合わせがあります。

3カスタム Converter を作成する

Employee String 表現を Employee インスタンスに変換する例を見てみましょう。

これが Employee クラスです。

public class Employee {

    private long id;
    private double salary;

   //standard constructors, getters, setters
}

String は、 id と__salaryを表す、コンマ区切りのペアになります。たとえば、“ 1,50000.00”です。

  • カスタムの Converter を作成するためには、 Converter <S、T> インターフェースを実装し、 convert() メソッドを実装する必要があります。

public class StringToEmployeeConverter
  implements Converter<String, Employee> {

    @Override
    public Employee convert(String from) {
        String[]data = from.split(",");
        return new Employee(
          Long.parseLong(data[0]),
          Double.parseDouble(data[1]));
    }
}

まだ終わっていません。 StringToEmployeeConverter FormatterRegistry に追加して、この新しいコンバーターについてSpringに通知する必要もあります。

これは、 WebMvcConfigurer を実装し、 addFormatters() メソッドをオーバーライドすることで実行できます。

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

以上です。私たちの新しい Converter ConversionService で利用できるようになり、他の組み込み Converter と同じ方法で使用できます。

@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. 暗黙的な変換

ConversionService を使用したこれらの明示的な変換以外に、Springはすべての登録済みコンバーターに対して、 Controller メソッド内で値を暗黙的に変換することもできます。

@RestController
public class StringToEmployeeConverterController {

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

これは __Converter __sを使うより自然な方法です。実際の動作を確認するためのテストを追加しましょう。

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

お分かりのように、テストはリクエストとレスポンスのすべての詳細を印刷します。応答の一部として返されるJSON形式の Employee オブジェクトは次のとおりです。

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

4 ConverterFactory を作成する

オンデマンドで __Converter sを作成する ConverterFactory を作成することも可能です。これは、 Enums 用の Converter __sを作成する際に特に役立ちます。

本当に単純なEnumを見てみましょう。

public enum Modes {
    ALPHA, BETA;
}

次に、 String を任意の Enum に変換するための __Converter sを生成できる StringToEnumConverterFactory__を作成しましょう。

@Component
public class StringToEnumConverterFactory
  implements ConverterFactory<String, Enum> {

    private static class StringToEnumConverter<T extends Enum>
      implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }

    @Override
    public <T extends Enum> Converter<String, T> getConverter(
      Class<T> targetType) {
        return new StringToEnumConverter(targetType);
    }
}

ご覧のとおり、ファクトリクラスは内部で Converter インターフェースの実装を使用しています。

ここで注意すべきことは、使用方法を示すために Modes Enum を使用しますが、 StringToEnumConverterFactory のどこにも Enum を記載していないことです。 私たちのファクトリクラスはどんな Enum タイプ でもオンデマンドで __Converter __sを生成するのに十分に一般的です。

次のステップは、前の例で Converter を登録したときにこのファクトリクラスを登録することです。

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

これで ConversionService __String sを Enum __sに変換する準備ができました。

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

5 GenericConverter を作成する

GenericConverter は、タイプセーフティを失うことを犠牲にして、より汎用的な用途のために Converter を作成するための柔軟性を提供します。

Integer Double 、または String BigDecimal 値に変換する例を考えてみましょう。このために3つの __Converterを記述する必要はありません。単純な GenericConverter__が目的を果たすことができます。

最初のステップは、どの種類の変換がサポートされているかをSpringに伝えることです。

これを行うには、 Set of ConvertiblePair を作成します。

public class GenericBigDecimalConverter
  implements GenericConverter {

@Override
public Set<ConvertiblePair> getConvertibleTypes () {

    ConvertiblePair[]pairs = new ConvertiblePair[]{
          new ConvertiblePair(Number.class, BigDecimal.class),
          new ConvertiblePair(String.class, BigDecimal.class)};
        return ImmutableSet.copyOf(pairs);
    }
}

次のステップは、同じクラスの convert() メソッドをオーバーライドすることです。

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

convert() メソッドは可能な限り単純です。ただし、 TypeDescriptor は、ソースとターゲットの種類に関する詳細を取得するという点で大きな柔軟性を提供します。

ご想像のとおり、次のステップはこの Converter を登録することです。

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

この Converter を使うことは、すでに見た他の例と似ています。

@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. 結論

このチュートリアルでは、Springの型変換システムの使用方法と拡張方法をさまざまな例で説明しました。

いつものように、この記事の全ソースコードはhttps://github.com/eugenp/tutorials/tree/master/spring-boot[over on GitHub]にあります。