[SpringBoot] TypeConverter 이해하기

BlackBean99·2022년 9월 10일
1
post-thumbnail

문자를 숫자로 변환하거나, 반대로 숫자를 문자로 변환해야 하는 것 처럼 애플리케이션을 개발하다 보면
타입을 변환해야 하는 경우가 상당히 많은데 그 TypeConverter를 이해하고 Custom하게 적용하는 방법까지 알아보자~

String data = request.getParameter("data");
        Integer intValue = Integer.valueOf(data);
        System.out.println("intValue = " + intValue);

Http 요청 파라미터는 모두 문자로 처리되는데 실제로 숫자 타입으로 사용할 경우를 다뤄보자!

   public String hello(HttpServletRequest request){ 

이렇게 파라미터를 받았을 때, 받는 형식을 Spring에서 제공하는 @RequestParam을 사용하면

   public String hello(@RequestParam Integer data){ 

이런식으로 선언해주면 스프링에서 중간에 타입을 변환해준다.

이런 경우는 @ModelAttribute , @PathVariable 에서도 동작한다.
이렇게 동작하는 TypeConverter는 통신할 때 받을 때 뿐 아니라 쓰는 경우가 또?

  • @Value 로 YML 읽기
  • XML 에 넣은 스프링 빈 정보 등록
  • 뷰 렌더링

org.springframework.core.convert.converter.Converter 를 사용해서 이 컨버터 인터페이스를 구현해서 사용하면

public interface Converter<S, T> {
T convert(S source);
}

모든 타입에 적용이 가능합니다.

만약에 내가 만든 특정 클래스의 형태로 변환하고 싶으면 어떻게 해야할까?

IP,PORT를 입력하면 IpPort객체로 변환하는 컨버터를 만들어보면서 이해해보자

IpPort 클래스 선언

@Getter
@EqualsAndHashCode
public class IpPort {

    private String ip;
    private int port;
    
    public IpPort(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }
}

롬복의 @EqualsAndHashCode 를 넣으면 모든 필드를 사용해서 equals() , hashcode() 를 생성한다.
따라서 모든 필드의 값이 같다면 a.equals(b) 의 결과가 참이 된다.

그럼 실제로 바꾸는 코드를 converter를 override 해서 사용해보자!

@Slf4j
public class StringToIpPortConverter implements Converter<String,IpPort> {

    @Override
    public IpPort convert(String source) {
        log.info("Converter soutce ={}", source);
        String[] split = source.split(",");
        String ip = split[0];
        int port = Integer.parseInt(split[1]);
        return new IpPort(ip, port);
    }
}

그럼 이런 의문이 듭니다. 아니 그냥 니가 바꾸는 거잖아. 왜 쓰는건데? 어차피 하나하나 다 변환해줄거면?

그래서 Spring 에서는 개별 Converter를 묶어서 편리하게 관리해주는 ConversionService 를 제공한다.

ConversionService

별도로 관리가 되지만 우리가 직접 커스텀한 컨버터를 등록시키고 해제하는 방법을 분리시켜서 DI 를 해서 사용해야 합니다

먼저 등록하는 방법 먼저 알아봅시다

Converter 등록

먼저 WebMvcConfigure에 있는 addFormaters 에 추가해주는 방법

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToIpPortConverter());
    }
}

참고로 이렇게 커스텀해서 만든 Converter은 Spring 내부에서 제공하는 수많은 Converter보다 우선순위가 높다는 점 알아두자.

동작과정
@RequestParam 은 @RequestParam 을 처리하는 ArgumentResolver 인
RequestParamMethodArgumentResolver 에서 ConversionService 를 사용해서 타입을 변환하는데 구체적인 내부 동작과정은 생략하게따

Formatter

이건 Converter는 범용으로 사용(객체 -> 객체, 객체 -> 문자, 문자 -> 숫자 ) 이런 형태로 사용했다.
이때, 만약에 요구사항
Integer -> String 출력 시점에 숫자 문자 "1,000" 이렇게 1000 단위에 쉼표를 넣어서 출력하거나, 또는 "1,000" 라는 문자를 1000 이라는 숫자로 변경해야 한다.
이런 조건이 있으면? 조금 띠용합니다..

이렇게 객체를 특정한 포멧에 맞추어 문자로 출력하거나 또는 그 반대의 역할을 하는 것에 특화된 기능이
바로 포맷터( Formatter )이다. 포맷터는 컨버터의 특별한 버전으로 이해하면 됩니다.

그럼 예시와 함께 Formatter 를 이해해봅시다

예제

1000 을 1,000으로 바꿔주는 Formatter를 만들어봅시다.
참고로 Number 는 Integer, Long의 부모 클래스입니다.
Locale 는 현지화 정보다. Local.KOREA 이런식으로 사용이 가능하다.

MyNumberFormatter 구현

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

parse를 통해서 문자를 객체로, print를 통해서 객체를 문자로 만들었습니다

Converter도 마찬가지로 ConversionService를 지원합니다.

ConversionService는 Converter만 지원하는데?
포매터를 지원하는 컨버전 서비스를 사용하면, 추가할 수 있는데 이는 내부에서 어뎁터 패턴을 통해서 Formatter가 Converter처럼 동작하게 만들어준다.

추가할 때 기본 포맷 말고 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); 를 선언해주면 Formatter도 ConversionService를 사용할 수 있다.

FormattingConversionService 상속관계

FormattingConversionService 는 ConversionService 관련 기능을 상속받기 때문에 결과적으로
컨버터도 포맷터도 모두 등록할 수 있다. 그리고 사용할 때는 ConversionService 가 제공하는 convert
를 사용하면 된다.

추가로 스프링 부트는 DefaultFormattingConversionService 를 상속 받은 WebConversionService
를 내부에서 사용합니다. 그래서 addFormatter로 등록할 수 있습니다.
registry.addFormatter(new MyNumberFormatter());
이런식으로 말이죠

Spring에서 제공하는 기능들

주력으로 사용하는 기능들을 소개해드리겠습니다.
Java에서는 수많은 Formatter를 제공하고 있는데 pattern 속성을 이용하면 아주 편리합니다.

  • @NumberFormat : 숫자 관련 형식 지정 포맷터 사용, NumberFormatAnnotationFormatterFactory
  • @DateTimeFormat : 날짜 관련 형식 지정 포맷터 사용

어노테이션 사용 예시

@Data
static class Form {
	@NumberFormat(pattern = "###,###")
	private Integer number;
	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	private LocalDateTime localDateTime;
}

이런식으로 넘겨줄 때 특정 형식으로 변환해서 제공하는데 편리한 기능을 제공한다라는거 기억해둡시다!

Reference

  • Document
  • 김영한 강사님 Inflearn MVC2편 Converter 내용 정리
profile
like_learning

0개의 댓글