Converter

기본적으로 클라이언트에서 오는 요청은 문자열의 형태로 전송된다. 이 때문에 코드를 작성할 때 먼저 문자열로 전송된 데이터를 원하는 형태에 맞게 변환을 진행해야 했다.

스프링은 이 과정을 추상화하여 제공하는데, 우리가 그동안 컨트롤러를 작성할 때 @ModelAttribute, @PathVariable, @RequestParam로 작성한 Argument에 맞게 타입을 변환하는 과정인 ArgumentResolver는 물론, @Value, ViewResolver를 통해 전달되는 변수 또한 Converter를 통해 변환과정을 거치게 된다.

기본적인 컨버터는 스프링이 제공하지만, 자신이 직접 만든 타입에 대해서는 경우에 따라 컨버팅 작업을 작성해야 할 수도 있는데, 이는 Converter 인터페이스를 상속받아 자신이 원하는 형태로 변환할 수 있도록 구현하면 된다.

Converter 인터페이스들

스프링은 다양한 방식의 타입 컨버터 인터페이스를 제공하는데, 자세한 내용은 공식 문서에서 확인할 수 있다.

Converter

package org.springframework.core.convert.converter;

@FunctionalInterface
public interface Converter<S, T> {
    @Nullable
    T convert(S source);
}
// 구현
final class StringToInteger implements Converter<String, Integer> {
    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

인터페이스를 확인해보면 함수형 인터페이스로 작성되어 있는 것을 확인할 수 있다. 이에 대한 자세한 설명은 Java8 - Lambda를 참고하면 된다.

간단한 S → T 형태의 람다식으로도 구성할 수 있으며, 인터페이스를 상속받아 자신이 원하는 형태로 변환할 수 있도록 구현할 수 있다.

ConverterFactory

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

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

    private final 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());
        }
    }
}

전체 클래스 계층에 대한 변환 로직을 한 번에 처리해야 하는 경우에 사용한다.

위의 예제를 보면 String → Enum의 형태로 변환하는 작업이란 것을 알 수 있는데, Enum을 상속한 자식 Enum에 대해서도 변환할 수 있도록 통합된 컨버터를 제공하는 것을 볼 수 있다.

GenericConverter

package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

세밀한 변환 로직이 필요할 때 사용한다.

공식 문서에서는 Array → Collection 변환에서 사용하는 것이 좋다고 되어 있으며, 이에 대한 대표적인 구현체는 ArrayToCollectionConverter이라고 한다.

  • getConvertibleTypes()
    • 해당 컨버터가 작동할 Source To Target의 Type을 저장하는 역할을 담당한다.
    • ConvertiblePair
      • sourceType, targetType을 저장하는 객체이다.
  • convert()
    • 해당 컨버터의 작동 로직을 구현하는 메서드이다.
    • TypeDescriptor
      • 대상에 대한 정보를 담는 객체이다.
      • 타입, 이름, 애노테이션, 클래스 등등에 대한 정보를 포함하고 있다.

ConfitionalGenericConverter

public interface ConditionalConverter {
    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

GenericConverter에 더해, 특정 조건이 참인 경우에만 변환 작업을 진행하고 싶은 경우에 사용하면 된다. 여기서 말하는 특정 조건은 다음을 이야기한다. 변환 대상은 물론 변환 결과에 대해서도 검사가 가능하다.

  • 특정 애노테이션이 붙어있는지
  • 특정 메소드가 선언되어 있는지
  • 특정 필드가 선언되어 있는지
  • 특정 클래스를 상속받고 있는지

공식 문서에서는 ConfitionalGenericConverter의 올바른 예제를 IdToEntityConverter라고 소개한다.

IdToEntityConverter는 변환 대상에 정적 찾기 메서드(findAccount(Long))를 구현했는지 검사하는 과정을 거치게된다.

주의

공식 문서에 따르면 GenericConverter는 더 복잡한 SPI(Service Provider Interface)이기 때문에 정말 필요할 때만 사용해야 하고, 기본 타입 변환의 경우엔 Converter나 ConverterFactory를 사용하라고 소개한다.

스프링 기본 Converter

스프링은 다양한 컨버터를 제공하고 있다. 그 중 많이 사용하는 것만 추려서 정리해보았다.

이름특징내용
ObjectToStringConverterObject → StringObject.toString(source)
StringToNumberConverterString → NumberNumberUtils.parseNumber(source, target.getClass())
EnumToStringConverterEnum → StringEnum.name(source)
StringToEnumConverterString → EnumEnum.valueOf(target.getClass(), source)
StringToBooleanConverterString → Booleantrue, on, yes, 1 → Boolean.true
false, off, no, 0 → Boolean.false
profile
백엔드 개발자 지망생

0개의 댓글