스프링 MVC 2편 - 스프링 타입 컨버터

링딩·2022년 8월 19일
0

스프링 MVC

목록 보기
17/18

김영한 강사님 강의를 참고하여 작성했습니다.

스프링 타입 컨버터 소개

HTTP 요청의 파라미터는 모두 문자열로 처리가 된다. 만약 숫자를 사용하고 싶다? 그렇다면 이렇게 타입을 변환해주어야 한다.


스프링에서는 파라미터에 @어노테이션을 이용해 HTTP 파라미터를 원하는 형태로 변환이 가능하다.

 @GetMapping("/hello-v2")
    public String helloV2(@RequestParam Integer data) {
        System.out.println("data = " + data);
        return "ok";
    }
  • HTTP 쿼리 스트링으로 전달하는 data=10 부분에서 10d은?
    -> 숫자 10(x), 문자 10(ㅇ)
  • 스프링이 제공하는 @RequestParam 을 사용하면 이 문자 10을 Integer 타입의 숫자 10으로 편리하게 받을 수 있다.
    => 이것은 스프링이 중간에서 타입을 변환해주었기 때문이다. 😁✌
    => @ModelAttribute , @PathVariable 에서도 도움을 받을 수 있다.

* 이런 타입 변환에서 매번 개발자가 직접 하나하나 타입 변환을 해야 한다면...?🤦‍♀️

  • 정말 괴로울 것이다.
  • 앞에서도 그랬지만 스프링이 중간에 타입 변환기를 사용해서 타입을 String - > Integer 로 변환해주었기 때문에 개발자는 편리하게 해당 타입을 바로 받을 수 있었다.



만약 개발자가 새로운 타입을 만들어서 변환하고 싶으면 어떻게 하면 될까?🤔



타입 컨버터 - Converter

[타입 컨버터를 사용하려면 ]
org.springframework.core.convert.converter.Converter 인터페이스를 구현하면 된다


컨버터

Integer -> String 컨버터

String -> Integer 컨버터


#### 테스트 ![](https://velog.velcdn.com/images/dabeen-jung/post/5d55de96-74e6-482e-96ce-c1dd596c9351/image.png)

IpPort 객체 컨버터 만들기

타입 컨버터 이해를 돕기 위해 조금 다른 컨버터를 준비해보았다.
127.0.0.1:8080 과 같은 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;
    }
}

IpPort 객체를 String으로 +) 그 반대도


테스트




컨버전 서비스 - ConversionService

이렇게 타입 컨버터를 하나하나 직접 찾아서 타입 변환에 사용하는 것은 매우 불편하다.

  • '스프링'은 용도에 따라 다양한 방식의 타입 컨버터를 제공한다.
  • 그래서 스프링은 '개별 컨버터'를 모아두고 그것들을 묶어서 편리하게 사용할 수 있는 기능을 제공하는데,
    => 이것이 바로 '컨버전 서비스( ConversionService )'이다.

ConversionService 인터페이스

package org.springframework.core.convert;
import org.springframework.lang.Nullable;
public interface ConversionService {
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor 
targetType);
<T> T convert(@Nullable Object source, Class<T> targetType);
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType,
TypeDescriptor targetType);
}

컨버전 서비스 인터페이스는 단순히 1. 컨버팅이 가능한가? 확인하는 기능과, 2.컨버팅 기능을 제공한다.


사용법은 간단하다.

  • 인터셉터처럼 @Configuration를 통해 스프링 ConversionService에 등록하고, 사용하면 된다.
    ➡ 스프링 내장 컨버터와 똑같은 동작(str->Integer)을 한다면, 아래와 같이 직접 추가한 컨버터를 먼저 사용한다.


@RequestParam 등 이제 우릭 만든 컨버터가 동작된다.

[처리과정]

  • @RequestParam 을 처리하는 ArgumentResolver 인 RequestParamMethodArgumentResolver 에서 ConversionService 를 사용해서 타입을 변환한다.




뷰 템플릿에 컨버터 적용하기

  • 타임리프는 렌더링 시에 컨버터를 적용해서 렌더링 하는 방법을 편리하게 지원한다.
    => 이전까지는 문자를 객체로 변환했다면, 이번에는 그 반대로 객체를 문자로 변환하는 작업을 확인할 수 있다

Controller

  • Model 에 숫자 10000 와 ipPort 객체를 담아서 뷰 템플릿에 전달

[templates/converter-view.html]

  • ${{...}}
    : '자동'으로 컨버전 서비스를 사용해서 변환된 결과를 출력

[헷갈리지 말자]

  • 변수 표현식 : ${...}
  • 컨버전 서비스 적용 : ${{...}}

[templates/converter-form.html]

  • 타임리프의 th:field자동으로 스프링 컨버터를 적용해서 HTTP 요청메시지를 보낸다.



Formatter 포맷터

  • Converter 는 입력과 출력 타입에 제한이 없는, 범용 타입 변환 기능을 제공한다.
  • 이렇게 객체를 특정한 포멧에 맞추어 문자로 출력하거나 또는 그 반대의 역할을 하는 것에 특화된 기능이 바로 '포맷터( Formatter )'이다.
    -> 포맷터는 컨버터의 특별한 버전으로 이해하면 된다.

Converter vs Formatter

  • 'Converter' 는 범용(객체 객체)
  • 'Formatter' 는 문자에 특화(객체 문자, 문자 객체) + 현지화(Locale) Converter 의 특별한 버전


포맷터를 지원하는 컨버전 서비스

  • '포맷터'를 지원하는 컨버전 서비스를 사용하면 컨버전 서비스에 포맷터를 추가할 수 있다.
    -> (내부) '어댑터 패턴'을 사용해서 FormatterConverter 처럼 동작하도록 지원
  • FormattingConversionService포맷터를 _지원하는 컨버전 서비스_이다.
    -> DefaultFormattingConversionServiceFormattingConversionService 에 기본적인 통화, 숫자 관련 몇가지 기본 포맷터를 추가해서 제공한다.
public class FormattingConversionServiceTest {
 @Test
 void formattingConversionService() {
 DefaultFormattingConversionService conversionService = new
DefaultFormattingConversionService();
 //컨버터 등록
 conversionService.addConverter(new StringToIpPortConverter());
 conversionService.addConverter(new IpPortToStringConverter());
 //포맷터 등록
 conversionService.addFormatter(new MyNumberFormatter());
 //컨버터 사용
 IpPort ipPort = conversionService.convert("127.0.0.1:8080",
IpPort.class);
 assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
 //포맷터 사용
 assertThat(conversionService.convert(1000,
String.class)).isEqualTo("1,000");
 assertThat(conversionService.convert("1,000",
Long.class)).isEqualTo(1000L);
 }
}

이후 WebConfig에서 포맷터까지 적용해주어야 한다!

  • 우선순위는 '컨버터' > '포맷터'
    -> '포맷터'가 적용되지 않고, 컨버터가 적용된다.
    -> StringToIntegerConverter , IntegerToStringConverter 를 꼭 주석처리



스프링이 제공하는 기본 포맷터


스프링은 자바에서 기본으로 제공하는 타입들에 대해 수 많은 포맷터를 기본으로 제공한다.
물론 나만의 포맷터가 필요하다면 이렇게 만들어서 등록하면 된다.
그러나 IDE에서 'Formatter 인터페이스의 구현 클래스'를 찾아보면 수 많은 '날짜' or '시간' 관련 포맷터가 제공되는 것을 확인할 수 있다.

but... '포맷터'는 기본 형식이 지정되어 있기 때문에, 객체의 각 필드마다 다른 형식으로 포맷을 지정하기는 어렵다.

.
.

스프링은 이런 문제를 해결하기 위해

'@' 기반으로 원하는 형식을 지정해서, 사용할 수 있는 매우 '유용한 포맷'터 두 가지를 기본으로 제공한다.


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

[Controller]

결과

• ${form.number}: 10000
• ${{form.number}}: 10,000
• ${form.localDateTime}: 2021-01-01T00:00:00
• ${{form.localDateTime}}: 2021-01-01 00:00:00



.
.

정리

컨버터를 사용하든, 포맷터를 사용하든 등록 방법은 다르지만, '사용할 때'는 컨버전 서비스를 통해서 일관성 있게 사용할 수 있다

  • 컨버전 서비스는 @RequestParam , @ModelAttribute , @PathVariable , '뷰 템플릿' 등에서 사용할 수 있다 ✨👍

주의 🧨

  • 메시지 컨버터( HttpMessageConverter )에는 '컨버전 서비스'가 적용되지 않는다
  • JSON 결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶다?
    -> 해당 라이브러리가 제공하는 설정을 통해서 포맷을 지정해야 한다.
    => 결과적으로 이것은 컨버전 서비스와 전혀 관계가 없다

스프링에서는 @XxxFormat(pattern="...")으로 기본 포맷터를 등록할 수 있게 구현해놓았다.

profile
초짜 백엔드 개린이

0개의 댓글