Search

Spring활용 - 타입컨버터

TypeConverter

문자를 숫자로 변환하거나, 반대로 숫자를 문자로 변환해야하는 것처럼
애플리케이션을 개발하다 보면 타입을 변환해야하는 경우가 상당히 많다.
HTTP 쿼리 스트링으로 전달되는 data=10 부분에서 10은 숫자가 아니라 문자10이다.
자바에서 다른 타입으로 변환해서 사용하고 싶으면 다음과 같이 숫자 타입으로 변환하는 과정을 거쳐야한다.
하지만!
@RequestParam을 사용하면 이 문자 10을 Integer타입의 숫자 10으로 편리하게 받는다.
→ 스프링이 중간에서 타입을 변환해주었기 때문
이런 것들은 @ModelAttribute, @PathVariable에서도 찾을 수 있다.
→ 그렇다면 개발자가 새로운 타입을 만들어서 반환하고 싶다면 어케해야할까?

컨버터 Interface

스프링은 확장 가능한 컨버터 인터페이스를 제공함
개발자는 스프링에 추가적인 타입 변환이 필요하면 이 컨버터 인터페이스를 구현해서 등록함
X → Y 타입으로 변환하는 컨버터 인터페이스
Y → X 타입으로 변환하는 컨버터 인터페이스를 만들어서 등록

타입 컨버터

org.springframework.core.convert.converter.Converter 인터페이스를 구현하면 된다.
Ex) StringToIntegerConverter
public class StringToIntegerConverter implements Converter<String,Integer> { @Override public Integer convert(String source){ log.info("convert source = {}",source); return Integer.valueOf(source); } }
Java
복사
Ex) IntegerToStringConverter
public class IntegerToStringConverter implements Converter<Integer,String> { @Override public String convert(Integer source){ log.info("convert source = {}", source); return String.valueOf(source); } }
Java
복사
Test
class ConverterTest { @Test void stringToInteger(){ StringToIntegerConverter converter = new StringToIntegerConverter(); Integer result = converter.conver("10"); assertThat(result).isEqualTo(10); } @Test void integerToString(){ IntegerToStringConverter converter = new IntegerToStringConverter(); String result = converter.convert(10); assertThat(result).isEqualTo("10"); } }
Java
복사

IpPort 객체로 변환하는 컨버터

IpPort
@Getter @EqualsAndHashCode public class IpPort{ private String ip; private int port; public IpPort(String ip, inti port){ this.ip = ip; this.port = port; } }
Java
복사
StringToIpPortConverter
public class StringToIpPortConvert implements Converter<String,IpPort>{ @Override public IpPort convert(String source){ String[] split = source.split(":"); String ip = split[0]; int port = Integer.parseInt(split[1]); return new IpPort(ip,port); } }
Java
복사
IpPortToStringConverter
public class IpPortToStringConverter implements Converter<IpPort, String> { @Override public String convert(IpPort source) { log.info("convert source={}", source); return source.getIp() + ":" + source.getPort(); } }
Java
복사
그런데 이렇게 타입 컨버터를 하나하나 직접 사용하면, 개발자가 직접 컨버팅하는 것과 큰 차이가 없다. 타입 컨버터를 등록하고 관리하면서 편리하게 변환 기능을 제공하는 역할을 하는 무언가가 필요함.
—> 컨버전 서비스 라는 걸 스프링이 제공한다.

용도에 따른 다양한 방식의 타입 컨버터

Converter → 기본 타입 컨버터
ConverterFactory → 전체 클래스 게층 구조가 필요할 때
GenericConverter → 정교한 구현, 대상 필드의 애노테이션 정보 사용 가능
ConditionalGenericConverter → 특정 조건이 참인 경우에만 발생

컨버전 서비스

개별 컨버터를 모아두고, 그것들을 묶어서 편리하게 사용할 수 있는 기능을 제공 → ConversionService
public interface ConversionService { boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType); boolean canConvert(@Nullable TypeDecriptor sourceType, TypeDecriptor targetType); <T> T convert(@Nullable Object source, Class<T> targetType); Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType); }
Java
복사

등록과 사용 분리

컨버터를 등록할 때는 StringToIntegerConverter 같읕 타입 컨버터를 명확하게 알아야 한다.
컨버터를 사용하는 입장에서는 타입 컨버터를 전혀 몰라도 된다. 타입 컨버터들은 모두 컨버전 서비스 내부에 숨어서 제공됨
사용자는 컨버전 서비스 인터페이스에만 의존하면 됨
물론 컨버전 서비스를 등록하는 부분과 사용하는 부분을 분리하고 의존관계 주입을 사용해야함.

인터페이스 분리 원칙

클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야한다.
DefaultConversionService 는 두 인터페이스를 구현
ConversionService: 컨버터 사용에 초점
ConverterRegistry: 컨버터 등록에 초점
인터페이스를 분리하면 컨버터를 사용하는 클라이언트와 컨버터를 등록하고 관리하는 관심사를 명확하게 분리할 수 있다. 컨버터를 사용하는 클라이언트는 ConversionService만 의존하면 되므로 컨버터를 어떻게 등록하고 관리하는지 전혀 몰라도 된다.

웹 앱에 Converter 적용

WebConfig - 컨버터 등록
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry){ registry.addConverter(new StringToIntegerConverteR()); registry.addConverter(new IntegerToStringConverteR()); registry.addConverter(new StringToIpPortConverter()); registry.addConverter(new IpPortToSTringConverter()); } }
Java
복사
Ex)사용 예
@GetMapping("/ip-port") public String ipPort(@RequestParam IpPort ipPort){ System.out.println("ipPort IP = " + ipPort.getIp()); System.out.println("ipPort PORT = " + ipPort.getPort()); return "ok"; }
Java
복사
?ipPort=127.0.0.1:8080에서 잘 파싱되서 넘어옴
이거는 @RequestParam, @ModelAttribute에도 적용된다.

뷰 템플릿에 컨버터 적용

이제는 객체를 문자로 변환하는 작업을 뷰 템플릿에서 할 수 있다.
1.
${{...}}
이렇게하면 넘어간 객체를 String으로 바꿔서 출력한다.
2.
th:field
th:field = "*{ipPort}"
이렇게 하면 필드를 컨버터를 적용해서 String으로 반환해서 던진다.

Formatter

포맷터 - Formatter
컨버터는 입력과 출력 타입에 제한이 없는 범용 타입 변환 기능을 제공한다.
다만 필요한 건 거의
문자 → 타입 & 타입 → 문자

Example

1000 → 문자 "1,000"이렇게
"1,000" → 1000 으로

Locale

여기에 추가로 날짜 숫자의 표현 방법은 Locale현지화 정보가 사용될 수 있다.
이렇게 객체를 특정한 포맷에 맞추어 문자로 출력하거나 또는 그 반대의 역할을 하는 것에 특화된 기능이 바로 포맷터이다.
Formatter : 문자에 특화 + 현지화
public interface Printer<T>{ String print(T object, Locale locale); } public interface Parser<T> { T parse(String text, Locale locale) throws ParseException; } public interface Formatter<T> extends Printer<T>, Parser<T> { }
Java
복사

Example

public class MyNumberFormatter implements Formatter<Number>{ @Override public Number parse(String text, Locale locale) throw ParseException{ log.info("text={},locale={}",text,locale); NumberFormat format = NumberFormat.getInstance(locale); return format.parse(text); } @Override public String print(Number object, Locale locale){ return NumberFormat.getInstance(locale).format(object); } }
Java
복사

스프링이 기본적으로 제공하는 포매터

@NumberFomat: 숫자 관련 형식 지정 포맷터
@DateTimeFormat: 날짜 관련 형식 지정 포맷터