Spring MVC (2) - 스프링 포맷터
1. Formatter란?
- Converter는 입력 및 출력 타입에 제한이 없는, 범용 타입 변환 기능을 제공함
- 일반적인 웹 애플리케이션 환경에서는 문자 - 타입간 변환이 많다
- 특히 Integer -> String (1000 -> "1,000") 혹은 ("1,000" -> 1000)
- 날짜 객체 ("2021-01-01 10:50:11")을 Date 객체로 변환하거나 그 반대 상황이 많다.
(특히 날짜 표현 방법은 Locale 현지화 정보가 사용될 수 있다.)
- 이러한 상황에서 문자-객체 사이 특화된 기능을 제공하는 것이 Formatter이다
*스프링은 용더에 따라 다양한 포맷터를 제공함
> Formatter
> AnnotationFormatterFactory -필드의 타입이나 애노테이션 정보를 활용할 수 있는 포맷터
2 Formatter만들기
- 포맷터의 경우 객체-문자 사이 두 가지 변환을 모두 제공한다.
String print(T object, Locale locale): 객체를 문자로 변환
T parse(String text, Locale locale) : 문자를 객체로 변환
public interface Printer<T> {
String print(T object, Locale locale);
}
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
- ISP분리 잘되어 있다.
public class MyNumberFormatter implements Formatter<Number>{
@Override
public Number parse(String text, Locale locale) throws ParseException{
NumberFormat format = NumberFormat.getInstance(locale);
return formt.parse(text);
}
@Override
public String print(Number object, Locale locale){
return NumberFormat.getInstance(locale).format(object);
}
}
- 자바가 기본으로 제공하는 NumberFormat을 사용하면, Locale정보를 활용해서 나라별 숫자 포맷을 만들어준다.
(parse -> textToInteger, format -> objectTotest)
MyNumberFormatter format = new MyNumberFormatter();
@Test
void parse() throws ParseException{
Number result = format.parse("1,000",Locale.KOREA);
assertThat(result).isEqualsTo(1000L);
}
@Test
void print(){
String result = format.print(1000,Locale.KOREA);
assertThat(result).isEqualTo("1,000");
}
- 컨버터에서 했던 것 처럼 포맷터도 직접 불러와서 포맷을 해봤다.
3. 포맷터를 지원하는 ConversionService
- 포맷터를 지원하는 컨버전 서비스를 사용하자, 포맷터를 추가할 수 있다.
- 내부에서 어댑터 패턴을 사용해서 Formatter가 Converter처럼 작동하도록 지원한다.
*FormattingConversionService가 포맷터 지원하는 컨버전 서비스임
DrfaultFormattingConversionService는 위 서비스에 기본적인 통화,숫자관련 기본 포맷터를 제공한다.
@Test
void formattingConversionService() {
DefaultFormattingConversionService conversionService = new
DefaultFormattingConversionService();
//포맷터 등록
conversionService.addFormatter(new MyNumberFormatter())
//포맷터 사용
conversionService.convert(1000,String.class)).isEqualTo("1,000")
}
- DefaultFormattingConversionService 상속관계
ConversionService를 상속받음 따라서 컨버터 포맷터 모두 등록가능하고, 사용시에는 convert만 사용함
* 스프링 부트는 DefaultFormattingConversionService를 상속한 WebConversionService 내부에서 사용
4. 포맷터 적용하기
- 웹 애플리케이션에 포맷터 사용해보자.
@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());
}
}
- 위 Integer -> String 변환하는 컨버터는 주석처리 (우선순위 때문에!)
5. 스프링이 제공하는 기본포맷터
- 스프링은 기본으로 많은 포맷터를 제공한다.
- 날짜와 시간과 관련된 포맷터를 제공한다.
- 포맷터는 기본 형식이 지정되어 있기 때문에, 객체의 각 필드마다 다른 형식으로 포맷을 지정하기 어렵다!
ex( 객체에 필드가 숫자,날짜 일때 form에서 해당 name으로 값이 넘어온다면
String -> 필드 두개 formatting해줘야함 필드에 대한 각각의 포맷터가 필요할 것이다 -> 만들고 등록 귀찮)
- 스프링은 이런 문제를 해결하기 위해 원하는 형식을 지정해서 사용가능한 매우 유용한 포맷터를 기본으로 제공함
@NumberFormat : 숫자 관련 형식 포맷터 (NumberFormatAnnotaionFormatterFactory)
@DateTimeFormat : 날짜 관련 형식 포맷터 (Jsr310DateTimeFormatAnnotationFormatterFactory)
*WebConfigurer를 통해 컨버전 서비스에 직접만든 포맷터 등록 말고 편하게 사용가능한 미리 만들어진 포맷터
//객체 만들어서 보냄 (Form 객체)
@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";
}
// Form으로 부터 값을 받아서 ArgumentResolver가 포맷팅해서 만들어줌
@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;
}
- form
<form th:object="${form}" th:method="post">
number <input type="text" th:field="*{number}"><br/>
localDateTime <input type="text" th:field="*{localDateTime}"><br/>
<input type="submit"/>
</form>
- html 출력
<ul>
<li>${form.number}: <span th:text="${form.number}" ></span></li>
<li>${{form.number}}: <span th:text="${{form.number}}" ></span></li>
<li>${form.localDateTime}: <span th:text="${form.localDateTime}" ></span></
li>
<li>${{form.localDateTime}}: <span th:text="${{form.localDateTime}}" ></
span></li>
</ul>
* 포맷터와 컨버터는 ArgumentResovler가 객체를 생성하기 전에 사용한다 ( RequestParamMethodArgumentResolver)
즉 객체의 필드 단위로 적용된다! (특히 사용자 지정 객체라면)
*주의
메시지 컨버터에는 컨버전 서비스가 적용되지 않음
HttpMessageConverter의 역할은 HTTP 메시지 바디의 내용을 객체로 변환하거나 객체를 HTTP 메시지 바디에 입력
JSON을 객체로 변환하는 메시지 컨버터는 내부에서 Jackson같은 라이브러리 사용함
객체를 JSON으로 변환한다면, 결과는 라이브러리에 달림 -> JSON결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶다면 라이브러리가 제공하는 설정을 통해서 포맷을 지정해야함
결론적으로 컨버전 서비스(컨버터,포맷터)는 @RequestParam, @ModelAttribute, @PathVariable,뷰 템플릿에서만 사용가능함