스프링 MVC 요청 파라미터
HTTP 요청 파라미터들은 모두 String으로 처리 된다.
@RequestParam , @ModelAttribute , @PathVariable 기존 String으로 들어온것들을 알아서 integer나 알맞은 형식으로 변환해준다.
스프링은 확장 가능한 컨버터 인터페이스를 제공한다.
org.springframework.core.convert.converter; 를 사용해야한다.
@Slf4j
public class IpPortToStringConverter implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
log.info("convert source={}", source);
//IpPort 객체 -> "127.0.0.1:8080"
return source.getIp() + ":" + source.getPort();
}
}
@Test
void stringToIpPort() {
IpPortToStringConverter converter = new IpPortToStringConverter();
IpPort source = new IpPort("127.0.0.1", 8080);
String result = converter.convert(source);
assertThat(result).isEqualTo("127.0.0.1:8080");
}
IpPort 객체를 알맞은 스트링으로 convert 하는 테스트 코드이다.
아직까지는 그냥 개발자가 수동으로 converting하는거랑 별 차이가 없다.
@ConversionService
스프링은 이러한 개별 컨버터를 모아서 그것들을 편리하게 사용할 수 있게 해준다.
public class ConversionServiceTest {
@Test
void conversionService() {
//등록
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToIntegerConverter());
conversionService.addConverter(new IntegerToStringConverter());
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
//사용
assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
assertThat(conversionService.convert(10, String.class)).isEqualTo("10");
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
String ipPortString = conversionService.convert(new IpPort("127.0.0.1", 8080), String.class);
assertThat(ipPortString).isEqualTo("127.0.0.1:8080");
}
}
ConversionService가 동작에 알맞은 converter를 찾아서 사용한다.
DefaultConversionService 는 위로 올라가보면 ConversionService ,ConveterRegistry 두가지 인터페이스를 구현하고 있다.
이렇게 컨버터를 사용하는 클라이언트와 컨버터를 등록하고 관리하는 클라이언트의 관심사를 명확하게 분리할 수 있다.
스프링에 Converter 적용하기
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
//주석처리 우선순위
// registry.addConverter(new StringToIntegerConverter());
// registry.addConverter(new IntegerToStringConverter());
registry.addConverter(new StringToIpPortConverter());
registry.addConverter(new IpPortToStringConverter());
//추가
registry.addFormatter(new MyNumberFormatter());
}
}
스프링에서 기본적으로 추가해논 Converter들이 존재한다. 하지만 새로운 컨버터를 추가하면 기본 컨버터보다 높은 우선순위를 가진다.
@RequestParam 는 ArgumenResolver 인 RequestParamMethodArgumentResolver 에서 ConversionService를 이용해 타입을 변환한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<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>
</ul>
</body>
</html>
타임리프에서 {}를 두번 쓰게되면 , number 같이, converter를 적용하겠다는 뜻이다.
<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>
th:field도 자동으로 converter를 적용을 해준다. th:value의 경우는 자동으로 적용되지 않는다.
Formatter
Converter는 입력과 출력타입에 제한이 없는 범용 타입 변환기능을 제공
1,000 을 1000으로 변경하고 싶은 상황 같을 때 쓴다.
이런식으로 객체를 특정한 포멧에 맞추어 출력하거나 또는 그 반대의 역할을 하는 것에 특화된 기능이 바로 포멧터 이다.
Formatter는 문자에 특화되었다 Converter의 특별한 버전이다. Locale을 통한 현지화도 가능하다.
@Slf4j
public class MyNumberFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text={}, locale={}", text, locale);
//"1,000" -> 1000
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);
}
}
NumberFormat 객체를 사용한다. 이 객체는 Locale 정보를 활용해서 나라별로 다른 숫자 포맷을 만들어준다.
포멧터를 지원하는 컨버전 서비스
FormattingConversionService 는 포맷터를 지원하는 컨버전 서비스이다.
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);
}
}
DefaultFormattingConversionService 는 Conversion 기능을 다 상속받은 FormattService이다.
스프링이 제공하는 기본 포멧터
@NumberFormat , @DateTimeFormat
@Controller
public class FormatterController {
@GetMapping("/formatter/edit")
public String formatterForm(Model model) {
Form form = new Form();
form.setNumber(10000);
form.setLocalDateTime(LocalDateTime.now());
model.addAttribute("form", form);
return "formatter-form";
}
@PostMapping("/formatter/edit")
public String formatterEdit(@ModelAttribute Form form) {
return "formatter-view";
}
@Data
static class Form {
@NumberFormat(pattern = "###,###")
private Integer number;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
}
}
패턴으로 주어진 방식으로 변환을 해준다.
저렇게 (10,000) 입력이들어와도 숫자로 인식해서 바꿔준다. 양방향인것이다.
HttpMessageConverter에는 ConversionService 적용되지 않는다.
특히 객체를 json으로 변환할 때 HttpMessageConverter 를 사용하면서 이 부분을 많이 오해한다. json 결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶으면 해당 라이브러리(Jackson)가 제공하는 설정을 통해서 포맷을 지정해야 한다.
'WEB > Spring MVC 2' 카테고리의 다른 글
Spring MVC 2편 파일 업로드 (0) | 2021.11.06 |
---|---|
Spring MVC 2편 API 예외 처리 (0) | 2021.10.30 |
Spring MVC 2편 예외처리와 오류페이지 (0) | 2021.10.23 |
Spring MVC 2편 로그인 처리2 - 필터, 인터셉터 (0) | 2021.10.16 |
Spring MVC 2편 로그인 처리1 쿠키 세션 (0) | 2021.10.10 |