기본적으로 클라이언트에서 오는 요청은 문자열의 형태로 전송된다. 이 때문에 코드를 작성할 때 먼저 문자열로 전송된 데이터를 원하는 형태에 맞게 변환을 진행해야 했다.
스프링은 이 과정을 추상화하여 제공하는데, 우리가 그동안 컨트롤러를 작성할 때 @ModelAttribute, @PathVariable, @RequestParam로 작성한 Argument에 맞게 타입을 변환하는 과정인 ArgumentResolver는 물론, @Value, ViewResolver를 통해 전달되는 변수 또한 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
형태의 람다식으로도 구성할 수 있으며, 인터페이스를 상속받아 자신이 원하는 형태로 변환할 수 있도록 구현할 수 있다.
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에 대해서도 변환할 수 있도록 통합된 컨버터를 제공하는 것을 볼 수 있다.
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
세밀한 변환 로직이 필요할 때 사용한다.
공식 문서에서는 Array → Collection 변환에서 사용하는 것이 좋다고 되어 있으며, 이에 대한 대표적인 구현체는 ArrayToCollectionConverter이라고 한다.
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를 사용하라고 소개한다.
스프링은 다양한 컨버터를 제공하고 있다. 그 중 많이 사용하는 것만 추려서 정리해보았다.
이름 | 특징 | 내용 |
---|---|---|
ObjectToStringConverter | Object → String | Object.toString(source) |
StringToNumberConverter | String → Number | NumberUtils.parseNumber(source, target.getClass()) |
EnumToStringConverter | Enum → String | Enum.name(source) |
StringToEnumConverter | String → Enum | Enum.valueOf(target.getClass(), source) |
StringToBooleanConverter | String → Boolean | true, on, yes, 1 → Boolean.true false, off, no, 0 → Boolean.false |