[Spring MVC] [2] 10. 스프링 타입 컨버터

윤경·2021년 9월 30일
2

Spring MVC

목록 보기
25/26
post-thumbnail

[1] 프로젝트 생성

프로젝트 생성


[2] 스프링 타입 컨버터 소개

숫자를 문자로, 문자를 숫자로 타입을 변환하는 방법을 알아보자.

HTTP 요청 파라미터는 모두 문자로 처리된다.
따라서 요청 파라미터를 자바에서 다른 타입으로 변환해 사용하고 싶다면 숫자 타입으로 변환하는 과정을 거쳐야된다.

@RequestParam

@RequestParam을 사용하면 문자 10을 Integer 타입의 숫자 10으로 편리하게 변환해준다.
스프링이 중간에서 타입 변환

@ModelAttribute, @PathVariable에서도 타입이 변환된 것을 확인할 수 있다.

스프링 타입 변환 적용 예시

  • 스프링 MVC 요청 파라미터 (@RequestPram, @ModelAttribute, @PathVariable)
  • @Value등으로 YML 정보 읽기
  • XML에 넣은 스프링 빈 정보를 변환
  • 뷰 렌더링

스프링은 확장 가능한 컨버터 인터페이스를 제공하므로 추가적인 타입 변경이 필요하다면 컨버터 인터페이스를 구현해 등록하면 된다.


[3] 타입 컨버터 - Converter

타입 컨버터를 사용하기 위해서는 ⭐️org.springframework.core.convert.converter.Converter 인터페이스를 구현하면 된다.
(Converter라는 이름의 인터페이스가 많아 위의 인터페이스를 사용해야 한다.)

Integer.valueOf(): 숫자로 변경
String.valueOf(): 문자로 변경

✔️ IP, PORT를 입력하면 IpPort 객체로 변환하는 컨버터
@EqualAndHashCode: 롬복 애노테이션으로 모든 필드를 사용해 equals(), hashcode()를 생성. 따라서 모든 필드의 값이 같다면 a.equals(b)의 결과가 참이 됨.

이렇게 타입 컨버터를 하나하나 직접 사용하면 번거로워 타입 컨버터를 등록하고 관리하며 편리하게 변환 기능을 제공하는 역할을 수행하는 무언가가 필요하다. 🤔

스프링은 용도에 따라 다양한 방식의 타입 컨버터를 제공

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

[4] 컨버전 서비스 - ConversionService

이렇게 컨버터를 하나하나 찾아 타입 변환에 사용하는 것은 매우 불편하므로 또 스프링이 제공하는 기능을 이용해보자.

ConversionService

스프링은 개별 컨버터를 모아두고 편리하게 관리할 수 있는 컨버전 서비스 ConversionService를 제공한다.

등록과 사용 분리
컨버터를 등록할 때는 StringToIntegerConverter같은 타입 컨버터를 명확하게 알아야 한다. 반면, 컨버터를 사용하는 입장에서는 타입 컨버터를 전혀 몰라도 된다.

타입 컨버터들은 모두 컨버전 서비스 내부에 숨어 제공된다. 따라서 컨버전 서비스 인터페이스에만 의존하면 된다.
물론 컨버전 서비스를 등록하는 부분과 사용하는 부분을 분리하고 의존관계 주입을 사용해야 한다.

컨버전 서비스 사용 예)

Integer value = conversionService.convert("123", Integer.class)
→ String을 Integer로

인터페이스 분리 원칙 ISP(Interface Segregation Principal)

: 클라이언트는 자신이 이용하지 않는 메소드에 의존하면 안됨

DefaultConversionServiceConversionService(컨버터 사용에 초점)과 ConverterRegistry(컨버터 등록에 초점) 인터페이스를 (쪼개어) 구현했다.

이렇게 인터페이스를 분리하면 관심사를 명확하게 분리할 수 있다. 특히, 컨버터를 사용하는 클라이언트는 ConversionService만 의존하면 되므로 어떻게 관리, 등록하는지 몰라도 된다. ➡️ ISP

즉, @RequestParam같은 곳에서 컨버전 서비스를 사용해 타입을 변환했다.


[5] 스프링에 Converter 적용하기

📌 기억 안 나는 날 위한 참고
@Configuration: 설정 파일을 만들기 위한 애노테이션 또는 Bean을 등록하기 위한 애노테이션

  • Bean을 등록할 때 싱글톤이 되도록 보장해준다.
  • 스프링 컨테이너에서 Bean을 관리할 수 있게 해준다.

스프링은 내부에서 ConversionService를 제공하고 우리는 WebMvcConfigurer가 제공하는 addFormatters()를 사용해서 추가하고 싶은 컨버터를 등록한다.

그런데 사실 StringToIntegerConverter 등록 전부터 잘 작동했다. 스프링은 생각보다 엄~청 많은 컨버터들을 기본으로 제공하기 때문이다.
하지만 나는 공부하기 위해 등록한 것 뿐이고 추가한 컨버터이기 때문에 우선순위가 높아 내가 등록한 컨버터가 동작하게 된 것이다.

처리 과정

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


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

객체를 문자로 변환하는 작업을 해보자.

    <li>${number}: <span th:text="${number}" ></span></li>
    <li>${{number}}: <span th:text="${{number}}" ></span></li>
    <li>${ipPort}: <span th:text="${ipPort}" ></span></li>
    <li>${{ipPort}}: <span th:text="${{ipPort}}" ></span></li>

타임리프는 ${{}}를 사용하면 자동으로 컨버전 서비스를 사용해 변환된 결과를 출력해준다.
(물론 스프링과 통합되어 스프링이 제공하는 컨버전 서비스를 사용하므로, 우리가 등록한 컨버터들을 사용할 수 있음)

${} VS ${{}}

${}: 변수 표현식 (컨버터 적용 X)
${{}}: 컨버전 서비스 적용 (컨버터 적용 O)

${{number}}: 뷰 템플릿은 데이터를 문자로 출력
따라서 컨버터를 적용하면 Integer타입 10000이 String 타입으로 변환되는 컨버터인 IntegerToStringConverter를 실행
이 부분은 컨버터를 실행하지 않아도 타임리프가 숫자를 문자로 자동 변환해주기 때문에 컨버터 적용 여부와 상관없이 결과가 같다.

${{ipPort}}: 뷰 템플릿은 데이터를 문자로 출력
따라서 컨버터를 적용하면 IpPort 타입을 String 으로 변환해야 하므로 IpPortToStringConverter가 적용됨


[7] 포맷터 - Formatter

Converter는 입력과 출력타입에 제한이 없는 범용 타입 변환 기능을 제공한다.

문자 → 다른 타입, 다른 타입 → 문자 이렇게 변환하는 경우가 많다.

Formatter Ex)

  • Integer → String의 경우 1000 → "1,000" (이런 일이 흔한편)
  • 날씨 객체를 "20xx-01-01 12:12:00"으로 출력하기 (또는 반대)

이렇게 객체를 특정한 포멧에 맞춰 문자로 출력, 또는 반대의 역할을 하는 것에 특화된 기능formatter라고 한다.
포멧터는 컨버터의 특별한 버전으로 이해하자.

Converter: 범용 (객체 → 객체)
Formatter: 문자에 특화 (객체 → 문자, 문자 → 객체) + 현지화(Locale)

✔️ Formatter interface
String print(T object, Locale locale): 객체를 문자로 변경
T parse(String text, Locale locale): 문자를 객체로 변경

1000 → "1,000"처럼 ,를 중간에 적용하려면 자바가 기본으로 제공하는 NumberFormat(Locale 정보를 활용해 나라별로 다른 숫자 포맷을 만들어줌) 객체를 사용하자.


참고로 isEqualTo(1000L)은 Long이기 때문에 L가 필요한 것


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

컨버전 서비스에는 컨버전만 등록하고 포맷터를 등록할 수는 없다.

그런데 포맷터는 문자에 특화된 특별한 컨버터일 뿐이므로 포맷터를 지원하는 컨버전 서비스를 사용하면 컨버전 서비스에도 포맷터를 추가할 수 있다.
내부에서 어댑터 패턴을 사용해 FormatterConverter처럼 동작하도록 지원한다.

FormattingConversionService: 포맷터를 지원하는 컨버전 서비스
DefaultFormattingConversionService: FormattingConversionService에 기본적인 통화, 숫자 관련 몇가지 기본 포맷터를 추가해 제공

DefaultFormattingConversionService 상속 관계
FormattingConversionServiceConversionService 관련 기능을 상속받기 때문에 결과적으로 컨버터도 포맷터도 모두 등록할 수 있다.

그리고 사용할 때는 ConversionService가 제공하는 convert를 사용하면 된다.

📌 추가로 스프링 부트는 DefaultFormattingConversionService를 상속받은 WebConversionService를 내부에서 사용한다.


[9] 포맷터 적용하기


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

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

그래서 스프링은 굉장히 유용한 포맷터 두 가지를 기본으로 제공한다.

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


📌

메시지 컨버터(HttpMessageConverter)에는 컨버전 서비스가 적용되지 않는다.

특히 객체를 Json으로 변환할 때 메시지 컨버터를 사용하며 이 부분을 많이 헷갈리는데 HttpMessageConverter의 역할은 Http 메시지 바디의 내용을 객체로 변환하거나 객체를 Http 메시지 바디에 입력하는 것이다.

예를 들어 json을 객체로 변환하는 메시지 컨버터는 내부에서 Jackson 같은 라이브러리를 사용한다. 객체를 json으로 변환했다면 그 결과는 이 라이브러리에 달렸다.
따라서 json 결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶으면 해당 라이브러리가 제공하는 설정을 통해 포맷을 지정해야 한다.

즉, 메시지 컨버터(HttpMessageConverter)는 컨버전 서비스와 전혀 관계가 없다.


profile
개발 바보 이사 중

0개의 댓글