Spring ‐ 스프링 타입 컨버터 - thought-corner/Backend-PlayGround GitHub Wiki

스프링 타입 컨버터

@FunctionalInterface
public interface Converter<S, T extends @Nullable Object> {

    /**
     * 소스 타입 S의 객체를 타겟 타입 T로 변환합니다.
     * @param source 변환할 소스 객체 (S의 인스턴스여야 하며, null이면 안 됨)
     * @return 변환된 객체 (T의 인스턴스이며, null일 수 있음)
     * @throws IllegalArgumentException 소스를 원하는 타겟 타입으로 변환할 수 없는 경우 발생
     */
    T convert(S source);

    /**
     * 현재 Converter를 적용한 후, 그 결과에 'after' Converter를 연쇄적으로 적용하는 
     * 합성(Composed) Converter를 생성합니다.
     * @since 5.3
     */
    default <U> Converter<S, @Nullable U> andThen(Converter<? super T, ? extends @Nullable U> after) {
        Assert.notNull(after, "'after' Converter must not be null");
        return (S s) -> {
            T initialResult = convert(s);
            // 첫 번째 변환 결과가 null이 아니면 두 번째 변환을 수행하고, null이면 null을 반환
            return (initialResult != null ? after.convert(initialResult) : null);
        };
    }
}

Converter 예시 코드 작성

@Slf4j
public class IntegerToStringConverter implements Converter<Integer, String> {

    @Override
    public String convert(Integer source) {
        log.info("converter source={}", source);
        
        // Integer 타입을 String 타입으로 변환하여 반환
        return String.valueOf(source);
    }
}
@Slf4j
public class StringToIntegerConverter implements Converter<String, Integer> {

    @Override
    public Integer convert(String source) {
        log.info("converter source={}", source);

        // String 타입을 Integer 타입으로 변환하여 반환
        // 문자가 숫자가 아닌 경우 NumberFormatException이 발생할 수 있음
        return Integer.valueOf(source);
    }
}
  • A → B, B → A 타입으로 변환하는 컨버터들을 직접 개발하면 매우 불편하다.
  • 이를 위해 스프링은 ConversionService를 제공한다. ConversionService는 컨버터를 모아두고 묶어서 편리하게 사용할 수 있는 기능을 제공한다.

ConversionService

public interface ConversionService {

    /**
     * sourceType에서 targetType으로 변환이 가능한지 여부를 확인합니다.
     */
    boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);

    /**
     * TypeDescriptor를 사용하여 변환 가능 여부를 확인합니다.
     * 필드나 속성의 애노테이션 정보 등 더 구체적인 컨텍스트를 포함할 수 있습니다.
     */
    boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

    /**
     * 주어진 소스 객체를 지정한 targetType으로 변환합니다.
     * @return 변환된 객체 (targetType의 인스턴스)
     */
    <T> @Nullable T convert(@Nullable Object source, Class<T> targetType);

    /**
     * 소스 객체를 타겟의 TypeDescriptor 정보에 맞춰 변환합니다. (Spring 6.1 이상)
     */
    default @Nullable Object convert(@Nullable Object source, TypeDescriptor targetType) {
        return convert(source, TypeDescriptor.forObject(source), targetType);
    }

    /**
     * 소스와 타겟의 상세 정보(TypeDescriptor)를 모두 사용하여 정밀하게 변환합니다.
     * 컬렉션이나 맵의 요소 타입까지 고려해야 할 때 주로 사용됩니다.
     */
    @Nullable Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, 
                             TypeDescriptor targetType);
}
// 등록 -> 추가적으로 여러 컨버터들 등록 가능
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new IntegerToStringConverter());
conversionService.addConverter(new IpPortToStringConverter());

//사용
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));

String ipPortString = conversionService.convert(new IpPort("127.0.0.1", 8080), String.class);
assertThat(ipPortString).isEqualTo("127.0.0.1:8080");
  • 위와 같이 등록해도 실제로 스프링에 등록되지 않는다.
  • 스프링에 등록하기 위해서는 WebMvcConfigurer가 제공하는 addFormatter()를 사용해서 컨버터를 스프링에 등록해주면 된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 주석: 개발자가 직접 만든 컨버터를 등록합니다.
        // 스프링은 내부적으로 ConversionService를 사용하며, 
        // 등록된 순서와 타입을 고려하여 적절한 변환기를 선택합니다.
        
        registry.addConverter(new StringToIntegerConverter());
        registry.addConverter(new IntegerToStringConverter());
        
        // 추가로 IpPortConverter 같은 사용자 정의 객체 변환기도 여기에 등록할 수 있습니다.
        // registry.addConverter(new StringToIpPortConverter());
    }
}

Formatter

  • Converter는 문자를 객체로, 객체를 문자로 변환하는 등의 범용적인 타입 변환 기능을 제공한다.
  • Formatter는 객체를 특정한 포맷에 맞추어 문자로 출력하거나 그 반대 기능을 할 수 있도록 한다.
public interface Formatter<T> extends Printer<T>, Parser<T> {

}
  • Printer 인터페이스의 print() 메서드 : 객체를 문자로 변환
  • Parser 인터페이스의 parser() 메서드 : 문자를 객체로 변환
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 1. 컨버터 등록: 소스 객체(S)를 타겟 객체(T)로 변환하는 범용적인 기능
        registry.addConverter(new StringToIntegerConverter());
        registry.addConverter(new IntegerToStringConverter());
        
        // 2. 포맷터 등록: 객체를 문자로, 문자를 객체로 변환하는 '문자 특화' 기능 (Locale 고려 가능)
        // 예: 날짜 형식을 "yyyy-MM-dd"로 출력하거나 숫자에 쉼표(,)를 찍는 등의 처리
        registry.addFormatter(new MyNumberFormatter()); 
        // 참고: 제공해주신 예시의 DateFormatter는 스프링 제공 구현체 또는 커스텀 구현체일 수 있습니다.
    }
}
  • Formatter 역시 위와 같이 WebMvcConfigurer 인터페이스에서 addFormatter() 메서드로 추가하면 된다.
  • 스프링에서는 이미 수많은 포맷터를 기본으로 제공해준다. 따라서 직접 만들어 사용할 일이 거의 없다.