Spring MVC(2) - 스프링 타입 컨버터
1. 타입 컨버터란?
- 문자를 숫자로 변환하거나, 숫자를 문자로 변환하는 일이 굉장히 많음
- 기존 스프링을 사용하면서 @RequestParam, @ModelAttribute, @Pathvariable에서 이게 가능했음 (자동)
추가로 @Value를 통해 정보읽기, XML에 넣은 스프링 빈 정보 변환 + 뷰 렌더링
(요청파라미터, @Value, 뷰렌더링)
- 개발자가 직접 새로운 타입을 정의하고, 이에 맞는 타입 컨버터를 등록하고 싶다면 어떻게 해야할까?
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
- 스프링은 확장 가능한 컨버터 인터페이스를 제공함!
- 추가적인 타입 변환이 필요하면 위 인터페이스를 구현하고, 등록하면 된다.
* (org.springframework.core.convert.converter.Converter를 import해서 사용하도록 주의하자)
2. 타입 컨버터 만들어보기
2.1 String,Integer
public class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String source){
return Integer.valueOf(source);
}
}
public class IntegerToStringConverter implements Converter<Integer,String>{
public String convert(Integer source){
return String.valueOf(source);
}
}
- 위와 같이 converter를 만들면, converter를 생성해서 자유롭게 활용 가능하다.
@Test
void stringToInteger(){
StringToIntegerConverter converter = new StringToIntegerConverter();
Integer result = converter.convert("10");
assertThat(result).isEqualTo(10);
}
2.2 사용자 정의 타입 컨버터
- 사용자 정의 타입
@Getter
@EqualsAndHashCode
public class IpPort {
private String ip;
private int port;
public IpPort(String ip, int port) {
this.ip = ip;
this.port = port;
}
}
- String, IpPort Converter
public class StringToIpPortConverter implements Converter<String, IpPort> {
public IpPort convert(String source){
String[] split = source.split(":");
String ip = split[0];
int port = Integer.parseInt(split[1]);
return new IpPort(ip,port);
}
}
public class IpPortToStringConverter implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
return source.getIp() + ":" + source.getPort();
}
}
> 타입 컨버터 인터페이스는 아주 단순해서 어렵진 않다!
> 이제 타입 컨버터를 등록하고 관리하면서 편리하게 변환 기능을 제공하는 걸 배워보자
3. 컨버전 서비스 - 컨버터 묶어서 편리하게 사용하기
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);
}
3.1 컨버전 서비스에 등록과 사용
@Test
void conversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
//등록
conversionService.addConverter(new StringToIntegerConverter());
conversionService.addConverter(new IntegerToStringConverter());
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
//사용
conversionService.convert("10",Integer.class);
}
- 등록하는 경우에는 StringToIntegerConverter와 같은 타입 컨버터를 명확하게 알아야함
- 사용하는 경우 정확하게 몰라도 됨 (컨버전 서비스 내부에 숨어서 제공됨)
- 따라서 등록하는 경우와 사용하는 경우 다른 인터페이스에 의존해서 등록과 사용하면됨
(구현체만 DefaultConversionService)
* DefaultConversionService : ConversionService (사용), ConverterRegistry(등록) 두개의 인터페이스 구현 (ISP)
4. 스프링에 Converter 적용하기
@Configuration
public class WebConfig implements WebMvcConfigurer{
public void addFormatters(FormatterRegistry regist){
registry.addConverter(new StringToIntegerConverter());
registry.addConverter(new IntegerToStringConverter());
registry.addConverter(new StringToIpPortConverter());
registry.addConverter(new IpPortToStringConverter());
}
}
- 스프링 내부에서 ConversionService를 제공함 따라서 WebMvcConfigurer에 addFormatters()를 사용해서 등록하면 됨
- 스프링 내부에서 사용하는 ConversionService에 컨버터 추가해줌
* 기본 컨버터보다, 직접 등록한 컨버터의 우선순위가 더 높다.
4.1 처리과정
- @RequestParam -> ArgumentResolver만남 (RequestParamMethodArgumentResolver)
- 얘가 Argument를 집어넣는 과정에서 ConversionService를 사용해서 타입 변환을 시도
5. 뷰 템플릿에 컨버터 적용하기
- 뷰에 컨버터를 적용하는 방법을 알아보자
- 타임리프는 이러한 렌더링을 편리하게 제공함
- 이제 객체 -> 문자 컨버팅
@GetMapping("/converter-view")
public String converterView(Model model) {
model.addAttribute("number", 10000);
model.addAttribute("ipPort", new IpPort("127.0.0.1", 8080));
return "converter-view";
}
-> 이렇게 view로 객체를 넘겨보자
<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>
-> ${}는 그냥 객체의 값을 사용하고 ${{}}을 사용하면, 컨버전 서비스의 컨버터를 사용해서 출력한다.
* String -> 객체 : 항상 컨버터를 거쳐서 특정 객체로 변환된다.
객체 -> String : 컨버터를 사용할 수도 있고 아닐 수도 있다.
5.1 폼에 적용하기
//폼에 기본값으로 넘겨주기 위해
@GetMapping("/converter/edit")
public String converterForm(Model model) {
IpPort ipPort = new IpPort("127.0.0.1", 8080);
Form form = new Form(ipPort);
model.addAttribute("form", form);
return "converter-form";
}
//폼에서 넘어온 값을 Converting
@PostMapping("/converter/edit")
public String converterEdit(@ModelAttribute Form form, Model model) {
IpPort ipPort = form.getIpPort();
model.addAttribute("ipPort", ipPort);
return "converter-view";
}
<form th:object="${form}" th:method="post">
th:field <input type="text" th:field="*{ipPort}"><br/>
th:value <input type="text" th:value="*{ipPort}">(보여주기 용도)<br/>
<input type="submit"/>
</form>
- 폼에 (input태그 등)에 값을 출력할때는 field를 사용하면 컨버팅 된 값을
보여주고 value를 사용하면 객체 값을 보여준다.
*th field는 id,name을 출력하는 등 다양한 기능 제공한다. 컨버전 서비스도 함께 적용됨