생략
문자를 숫자로 변환하거나, 반대로 숫자를 문자로 변환해야 하는 것 처럼 어플리케이션을 개발하다 보면 타입을 변환해야 하는 경우가 상당히 많다.
위의 경우 파라미터를 받을때 문자로 받는 것을 확인할 수 있다.
따라서 요청 파라미터를 자바에서 다른타입으로 변환해서 사용하고 싶다면, 위와 같이 숫자 타입으로 변환하는 과정을 거쳐야 한다.
이렇게 @RequestParam
을 사용하면 Integer
타입으로 받을 수 있는데,
그 이유는 @RequestParam
이 스프링 내부에서 문자타입을 숫자타입으로 바꿔주었기 때문이다.
즉, 처음에 받는
localhost:8080/hello-v2?data=123
에서
123은 문자이다.
이러한 예시는 @ModelAttribute
, @PathVariable
에서도 확인할 수 있다.
스프링의 타입 변환 적용 예
@RequestParam
, @ModelAttribute
, @PathVariable
@Value
등으로 YML 정보 읽기이렇듯, 타입을 변환해야 하는 경우는 상당히 많다. 개발자가 직접 하나하나 타입 변환을 해야 한다면, 정말 많은 작업을 해야한다.
스프링이 중간에 타입 변환기를 사용해서 String
-> Integer
로 변환해주었기 때문에 개발자는 편리하게 해당 타입을 바로 받을 수 있다. 앞에서는 문자를 숫자로 변경하는 예시를 들었지만, 반대로 숫자를 문자로 변경하는 것도 가능하고, Boolean
타입을 숫자로 변경하는 것도 가능하다.
만약 개발자가 새로운 타입을 만들어서 변환하고 싶으면 어떻게 하면 될까?
컨버터 인터페이스
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
과거에는 PropertyEditor
라는 것으로 타입을 변환했다. PropertyEditor
는 동시성 문제가 있어서 타입을 변환할 때마다 객체를 계속 생성해야 하는 단점이 있다. 지금은 Converter
의 등장으로 해당 문제들이 해결되었고, 기능 확장이 필요하면 Converter
를 사용하면 된다.
문자를 숫자로
숫자를 문자로
테스트
새로운 자바 클래스 IpPort
String을 IpPort로
IpPort를 String으로
테스트
그런데 이를 위와 같이 사용하기 위해서는 해당 컨버터 클래스를 Autowired
로 등록하고 넣는 것은 직접 컨버팅하는 것과 큰 차이가 없다.
이를 좀 더 편리하게 사용할 수 있는 뭔가가 필요하다.
참고
스프링은 용도에 따라 다양한 타입 컨버터를 제공한다.
Converter
-> 기본 타입 컨버터
ConverterFactory
-> 전체 클래스 계층 구조가 필요할 때
GenericConverter
-> 정교한 구현, 대상 필드의 어노테이션 정보 사용 기능
ConditionalGenericConverter
-> 특정 조건이 참인 경우에만 실행
DefaultConversionService
는 ConversionService
인터페이스를 구현한 것이고, 추가로 컨버터를 등록하는 기능도 제공한다.
등록과 사용을 분리
컨버터를 등록할 때는 StringToIntegerConverter
같은 타입 컨버터를 명확하게 알아야 한다. 반면에 컨버터를 사용하는 입장에서는 타입 컨버터를 몰라도 된다.
그러니, 사용할때는 그냥 conversionService
를 통해서 그냥 사용하면 된다.
물론 autowired
를 통한 의존관계 주입을 하여 사용해야한다.
인터페이스 분리 원칙 - ISP(Interface Segregation Principal)
인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
DefaultConversionService
는 다음 두 인터페이스를 구현했다.
ConversionService
: 컨버터 사용에 초점ConverterRegistry
: 컨버터 등록에 초점이렇게 인터페이스를 분리하면 컨버터를 사용하는 클라이언트와 컨버터를 등록하고 관리하는 클라이언트의 관심사를 명확하게 분리할 수 있다.
특히 컨버터를 사용하는 클라이언트는 ConvertService
만 의존하면 되므로, 컨버터를 어떻게 등록하고 관리하는지는 전혀 몰라도 된다.
이렇게 인터페이스를 분리한느 것을 ISP
라고 한다.
스프링은 내부에서 ConversionService
를 제공한다.
WebMvcConfigurer
가 제공하는 addFormatters()
를 사용해서 추가하고 싶은 컨버터를 등록하면 된다.
실행하면 위의 /hello-v2
를 했을때, StringToIntegerConverter
가 호출되는 것을 확인할 수 있다.
그런데 사실 이 컨버터가 없을때도 잘 실행이 된다.
그 이유는 스프링이 내부에서 수 많은 기본 컨버터들을 제공하기 때문이다.
컨버터를 추가하면 추가한 컨버터가 기본 컨버터보다 높은 우선순위를 가지게 된다.
이번에는 스프링이 처리할 수 없는 ipPort
를 넣어보자.
http://localhost:8080/ip-port?ipPort=127.0.0.1:8080
로 들어갔을때, IpPort
가 컨버터를 통해 분리되는 것을 확인할 수 있다.
처리 과정
@RequestPara
은 @RequestParam
을 처리하는 ArgumentResolver
인 RequestParamMethodArgumentResolver
에서 ConversionService
를 사용해서 타입을 변환한다.
부모 클래스와 다양한 외부 클래스를 호출하는 등 복잡한 내부 과정을 거치기 때문에 대략 이렇게 처리되는 것으로 이해해도 충분하다.
더 깊이있게 확인하고 싶으면 IpPortConvert
에 디버그 브레이크 포인트를 걸어서 확인해보자.
디버거 브레이크는 위 사진과 같이 빨간색 동그라미가 나오도록 클락하고 오른쪽 위에 벌레모양 버튼을 통해 디버그 모드로 실행하면 된다.