- Spring mvc2 타임리프 기본기능
- Spring mvc2 타임리프 스프링 통합과 폼
- Spring mvc2(2) 메세지, 국제화
- Spring mvc2(3) 검증1 validation
- Spring mvc2 검증2 1 bean validation
- Spring mvc2 검증2 2 로그인 처리1 쿠키, 세션
- Spring mvc2 검증2 3 로그인 처리2 필터, 인터셉터
- Spring mvc2 검증2 4 예외 처리와 오류 페이지
- Spring mvc2(1) api 예외처리
- Spring mvc2(2) 스프링 타입 컨버터
- Spring mvc2(3) 파일 업로드
Spring mvc2(2) 스프링 타입 컨버터
1. 스프링 타입 컨버터 소개
- 문자를 숫자로 변환하거나, 반대로 숫자를 문자로 변환해야 하는 것 처럼 애플리케이션을 개발하다 보면 타입을 변환해야 하는 경우가 상당히 많다.
- 하지만 스프링은 자동으로 변환해준다.
HelloController
-
@GetMapping("/hello-v2") public String helloV2(@RequestParam Integer data) { System.out.println("data = " + data); return "ok"; }
- 실행 :
http://localhost:8080/hello-v2?data=10
- 결과 : “ok”
- 실행 :
-
이 HTTP 쿼리 스트링으로 전달하는 data=10 부분에서 10은 숫자 10이 아니라 문자 “10”이다.
-
스프링이 제공하는 @RequestParam 을 사용하면 이 문자 10을 Integer 타입의 숫자 10으로 편리하게 받을 수 있다.
- 이것은 스프링이 중간에서 타입을 변환해주었기 때문이다.
스프링과 타입 변환
-
스프링은 확장 가능한 컨버터 인터페이스를 제공한다.
-
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S source); }
- 개발자는 스프링에 추가적인 타입 변환이 필요하면 이 컨버터 인터페이스를 구현해서 등록하면 된다.
2. 타입 컨버터 - Converter
- 타입 컨버터를 사용하려면
org.springframework.core.convert.converter.Converter
인터페이스를 구현하면 된다.
Converter 라는 이름의 인터페이스가 많으니 조심해야 한다.
사용자 정의 타입 컨버터
- 127.0.0.1:8080 과 같은 IP, PORT를 입력하면 IpPort 객체로 변환하는 컨버터를 만들어보자
IpPort
- “127.0.0.1:8080”
-
롬복의 @EqualsAndHashCode 를 넣으면 모든 필드를 사용해서 equals() , hashcode() 를 생성한다. 따라서 모든 필드의 값이 같다면 a.equals(b) 의 결과가 참이 된다.
-
package hello.typeconverter.converter.type; import lombok.EqualsAndHashCode; import lombok.Getter; import org.springframework.web.bind.annotation.GetMapping; @Getter @EqualsAndHashCode public class IpPort { private String ip; private int port; public IpPort(String ip, int port) { this.ip = ip; this.port = port; } }
StringToIpPortConverter - 컨버터
-
127.0.0.1:8080 같은 문자를 입력하면 IpPort 객체를 만들어 반환한다.
-
package hello.typeconverter.converter; import hello.typeconverter.converter.type.IpPort; import lombok.extern.slf4j.Slf4j; import org.springframework.core.convert.converter.Converter; @Slf4j public class StringToIpPortConverter implements Converter<String, IpPort> { @Override public IpPort convert(String source) { log.info("convert source={}", source); String[] split = source.split(":"); String ip = split[0]; int port = Integer.parseInt(split[1]); return new IpPort(ip, port); } }
ConverterTest - IpPort 컨버터 테스트 추가
-
IpPort 객체를 입력하면 127.0.0.1:8080 같은 문자를 반환한다.
-
package hello.typeconverter.converter; import hello.typeconverter.converter.type.IpPort; import lombok.extern.slf4j.Slf4j; import org.springframework.core.convert.converter.Converter; @Slf4j public class IpPortToStringConverter implements Converter<IpPort, String> { @Override public String convert(IpPort source) { log.info("convert source={}", source); return source.getIp() + ":" + source.getPort(); } }
ConverterTest - IpPort 컨버터 테스트
-
@Test void stringToIpPort() { StringToIpPortConverter converter = new StringToIpPortConverter(); String source = "127.0.0.1:8080"; IpPort result = converter.convert(source); assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080)); } @Test void ipPortToString() { 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"); }
- 그런데 이렇게 타입 컨버터를 하나하나 직접 사용하면, 개발자가 직접 컨버팅 하는 것과 큰 차이가 없다.
- 타입 컨버터를 등록하고 관리하면서 편리하게 변환 기능을 제공하는 역할을 하는 무언가가 필요하다.
스프링이 제공하는 다양한 방식의 타입 컨버터
- Converter : 기본 타입 컨버터
- ConverterFactory : 전체 클래스 계층 구조가 필요할 때
- GenericConverter : 정교한 구현, 대상 필드의 애노테이션 정보 사용 가능
- ConditionalGenericConverter : 특정 조건이 참인 경우에만 실행
스프링은 문자, 숫자, 불린, Enum등 일반적인 타입에 대한 대부분의 컨버터를 기본으로 제공한다. IDE에서 Converter , ConverterFactory , GenericConverter 의 구현체를 찾아보면 수 많은 컨버터를 확인할 수 있다.
3. 컨버전 서비스 - ConversionService
- 스프링은 개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용할 수 있는 기능을 제공하는데, 이것이 바로 컨버전 서비스( ConversionService )이다.
ConversionService 인터페이스
-
package org.springframework.core.convert; import org.springframework.lang.Nullable; 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); }
- 컨버전 서비스 인터페이스는 단순히 컨버팅이 가능한지 확인하는 기능과 컨버팅 기능을 제공한다.
ConversionServiceTest - 컨버전 서비스 테스트 코드
-
package hello.typeconverter.converter; import hello.typeconverter.converter.type.IpPort; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.core.convert.support.DefaultConversionService; import static org.assertj.core.api.Assertions.*; public class ConversionServiceTest { @Test void conversionService(){ //등록 DefaultConversionService conversionService = new DefaultConversionService(); conversionService.addConverter(new StringToIpPortConverter()); conversionService.addConverter(new IpPortToStringConverter()); conversionService.addConverter(new StringToIntegerConverter()); conversionService.addConverter(new IntegerToStringConverter()); //사용 assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10); assertThat(conversionService.convert(10, String.class)).isEqualTo("10"); IpPort result = conversionService.convert("127.0.0.1:8080", IpPort.class); assertThat(result).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"); } }
- DefaultConversionService 는 ConversionService 인터페이스를 구현했는데, 추가로 컨버터를 등록하는 기능도 제공한다.
인터페이스 분리 원칙 - ISP(Interface Segregation Principle)
등록과 사용 분리
- 컨버터를 등록할 때는 StringToIntegerConverter 같은 타입 컨버터를 명확하게 알아야 한다.
- 반면에 컨버터를 사용하는 입장에서는 타입 컨버터를 전혀 몰라도 된다. 타입 컨버터들은 모두 컨버전 서비스 내부에 숨어서 제공된다.
- 따라서 타입을 변환을 원하는 사용자는 컨버전 서비스 인터페이스에만 의존하면 된다.
- 물론 컨버전 서비스를 등록하는 부분과 사용하는 부분을 분리하고 의존관계 주입을 사용해야 한다.
인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
- DefaultConversionService 는 다음 두 인터페이스를 구현했다.
- ConversionService : 컨버터 사용에 초점
- ConverterRegistry : 컨버터 등록에 초점
- 이렇게 인터페이스를 분리하면 컨버터를 사용하는 클라이언트와 컨버터를 등록하고 관리하는 클라이언트의 관심사를 명확하게 분리할 수 있다.
- 특히 컨버터를 사용하는 클라이언트는 ConversionService 만 의존하면 되므로, 컨버터를 어떻게 등록하고 관리하는지는 전혀 몰라도 된다.
- 결과적으로 컨버터를 사용하는 클라이언트는 꼭 필요한 메서드만 알게된다.
즉, 사용하는 사람은 어떻게 등록하는지 몰라도 된다는 말. 이렇게 인터페이스를 분리하는 것을 ISP 라 한다.
4. 스프링에 Converter 적용하기
- 웹 애플리케이션에 Converter 를 적용해보자.
WebConfig - 컨버터 등록
-
package hello.typeconverter; import ..; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToIpPortConverter()); registry.addConverter(new IpPortToStringConverter()); registry.addConverter(new StringToIntegerConverter()); registry.addConverter(new IntegerToStringConverter()); } }
-
스프링은 내부에서 ConversionService 를 제공한다.
-
우리는 WebMvcConfigurer 가 제공하는 addFormatters() 를 사용해서 추가하고 싶은 컨버터를 등록하면 된다.
-
이렇게 하면 스프링은 내부에서 사용하는 ConversionService 에 컨버터를 추가해준다.
컨버터를 추가하면 추가한 컨버터가 기본 컨버터 보다 높은 우선순위를 가진다.
HelloController
-
@GetMapping("/ip-port") public String ipPort(@RequestParam IpPort ipPort) { System.out.println("ipPort.getIp() = " + ipPort.getIp()); System.out.println("ipPort.getPort() = " + ipPort.getPort()); return "ok"; }
실행
http://localhost:8080/ip-port?ipPort=127.0.0.1:8080
- ipPort IP = 127.0.0.1 / ipPort PORT = 8080
- ?ipPort=127.0.0.1:8080 쿼리 스트링이 @RequestParam IpPort ipPort 에서 객체 타입으로 잘 변환 된 것을 확인할 수 있다.
처리과정
- @RequestParam 은 @RequestParam 을 처리하는 ArgumentResolver 인 RequestParamMethodArgumentResolver 에서 ConversionService 를 사용해서 타입을 변환한다.
- 만약 더 깊이있게 확인하고 싶으면 IpPortConverter 에 디버그 브레이크 포인트를 걸어서 확인해보자.
5. 뷰 템플릿에 컨버터 적용하기
- 타임리프는 렌더링 시에 컨버터를 적용해서 렌더링 하는 방법을 편리하게 지원한다.
- 이전까지는 문자를 객체로 변환했다면, 이번에는 그 반대로 객체를 문자로 변환하는 작업을 확인할 수 있다.
ConverterController
-
@Controller public class ConverterController { @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"; } }
- Model 에 숫자 10000 와 ipPort 객체를 담아서 뷰 템플릿에 전달한다.
converter-view.html
-
resources/templates/converter-view.html
-
<!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>$: <span th:text="$" ></span></li> <li>${ipPort}: <span th:text="${ipPort}" ></span></li> <li>$: <span th:text="$" ></span></li> </ul> </body> </html>
타임리프는 $ 를 사용하면 자동으로 컨버전 서비스를 사용해서 변환된 결과를 출력해준다.
- 변수 표현식 : ${…}
- 컨버전 서비스 적용 : $
실행결과
- $ -> ipPortToStringConverter 적용
- $ -> IntegerToStringConverter 적
폼에 적용하기
ConverterController - 코드 추가
-
package hello.typeconverter.controller; import ...; @Controller public class ConverterController { @GetMapping("/converter/edit") public String convertForm(Model model) { IpPort ipPort = new IpPort("127.0.0.1", 8080); Form form = new Form(ipPort); model.addAttribute("form", form); return "converter-form"; } @PostMapping("/converter/edit") public String converterEdit(@ModelAttribute Form form, Model model) { IpPort ipPort = form.getIpPort(); model.addAttribute("ipPort", ipPort); return "converter-view"; } //Form 객체를 데이터를 전달하는 폼 객체로 사용한다. @Data static class Form { private IpPort ipPort; public Form(IpPort ipPort) { this.ipPort = ipPort; } } }
/converter-form.html
-
resources/templates/converter-form.html
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <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> </body> </html>
- 타임리프의 th:field 는 앞서 설명했듯이 id , name 를 출력하는 등 다양한 기능이 있는데, 여기에 컨버전 서비스도 함께 적용된다.
- 템플릿 :
th:field <input type="text" th:field="*{ipPort}"><br/>
- HTML 에서 :
th:field <input type="text" id="ipPort" name="ipPort" value="127.0.0.1:8080"><br/>
- 템플릿 :
- ipPort 를 그대로 쓰고 싶으면
th:value="*{ipPort}"
라고 value 값으로 넣어야 한다.
- 타임리프의 th:field 는 앞서 설명했듯이 id , name 를 출력하는 등 다양한 기능이 있는데, 여기에 컨버전 서비스도 함께 적용된다.
converter/edit 화면
converter/edit 에서 form 전송 시
- String 값인 “127.0.0.1:8080” 가 IpPort 로 변경된다 (IpPortToStringConverter)
@ModelAttribute Form form
에 IpPort 값이 담긴다.- Post 로 받는다.
6. 포맷터 - Formatter
- Converter 는 입력과 출력 타입에 제한이 없는, 범용 타입 변환 기능을 제공한다.
- 하지만 개발자 입장에서는 문자를 다른 타입으로 변환하거나, 다른 타입을 문자로 변환하는 상황이 대부분이다.
웹 애플리케이션에서 객체를 문자로, 문자를 객체로 변환하는 예
- 화면에 숫자를 출력해야 하는데, Integer String 출력 시점에 숫자 1000 문자 “1,000” 이렇게 1000 단위에 쉼표를 넣어서 출력하거나, 또는 “1,000” 라는 문자를 1000 이라는 숫자로 변경해야 한다.\
- 날짜 객체를 문자인 “2021-01-01 10:50:11” 와 같이 출력하거나 또는 그 반대의 상황
Converter vs Formatter
- Converter 는 범용(객체 -> 객체)
- Formatter 는 문자에 특화(객체 문자, 문자 객체) + 현지화(Locale) Converter 의 특별한 버전
- Locale : 날짜 숫자의 표현 방법은 Locale 현지화 정보가 사용될 수 있다.
포맷터 - Formatter 만들기
Formatter 인터페이스
-
public interface Formatter<T> extends Printer<T>, Parser<T> { } public interface Printer<T> { String print(T object, Locale locale); } public interface Parser<T> { T parse(String text, Locale locale) throws ParseException; }
- Formatter 는 Printer 와 Parser 인터페이스를 상속받는다.
- 각각 print, parse 기능이 있다. (ISP 원칙)
- String print(T object, Locale locale) : 객체를 문자로 변경한다.
- T parse(String text, Locale locale) : 문자를 객체로 변경한다.
MyNumberFormatter
-
숫자 1000 을 문자 “1,000” 으로 그러니까, 1000 단위로 쉼표가 들어가는 포맷을 적용해보자. 그리고 그 반대도 처리해주는 포맷터를 만들어보자.
-
package hello.typeconverter.formatter; import lombok.extern.slf4j.Slf4j; import org.springframework.format.Formatter; import java.text.NumberFormat; import java.text.ParseException; import java.util.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); } }
implements Formatter<Number>
: String 은 기본이니까, 나머지 하나의 타입을 넣어준다.- “1,000” 처럼 숫자 중간의 쉼표를 적용하려면 자바가 기본으로 제공하는
NumberFormat
객체를 사용하면 된다. - NumberFormat 에
public Number parse(String source)
메서드가 있다. - 또한
public final String format(double number)
메서드가 있다. - 두개를 활용해서 변경하면 된다.
MyNumberFormatterTest
-
package hello.typeconverter.formatter; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import java.text.ParseException; import java.util.Locale; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class MyNumberFormatterTest { MyNumberFormatter formatter = new MyNumberFormatter(); @Test void parse() throws ParseException { Number result = formatter.parse("1,000", Locale.KOREA); assertThat(result).isEqualTo(1000L); //long 타입 주의 } @Test void print() { String result = formatter.print(1000, Locale.KOREA); assertThat(result).isEqualTo("1,000"); } }
- parse() 의 결과가 Long 이기 때문에 isEqualTo(1000L) 을 통해 비교할 때 마지막에 L 을 넣어주어야 한다.
실행 결과 로그
- MyNumberFormatter - text=1,000, locale=ko_KR
- MyNumberFormatter - object=1000, locale=ko_KR
참고
-
스프링은 용도에 따라 다양한 방식의 포맷터를 제공한다.
-
Formatter 포맷터
AnnotationFormatterFactory 필드의 타입이나 애노테이션 정보를 활용할 수 있는 포맷터
7. 포맷터를 지원하는 컨버전 서비스
-
컨버전 서비스에는 컨버터만 등록할 수 있고, 포맷터를 등록할 수는 없다.
-
그런데 생각해보면 포맷터는 객체 <-> 문자로 변환하는 특별한 컨버터일 뿐이다.
-
포맷터를 지원하는 컨버전 서비스를 사용하면 컨버전 서비스에 포맷터를 추가할 수 있다. 내부에서 어댑터 패턴을 사용해서 Formatter 가 Converter 처럼 동작하도록 지원한다.
FormattingConversionService는 포맷터를 지원하는 컨버전 서비스이다.
-
DefaultFormattingConversionService 는 FormattingConversionService 에 기본적인 통화, 숫자 관련 몇가지 기본 포맷터를 추가해서 제공한다.
DefaultFormattingConversionService 테스트
-
package hello.typeconverter.formatter; 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 상속 관계
- FormattingConversionService 는 ConversionService 관련 기능을 상속받기 때문에 결과적으로 컨버터도 포맷터도 모두 등록할 수 있다.
- 그리고 사용할 때는 ConversionService 가 제공하는 convert 를 사용하면 된다. (converter 든, format 이든!)
추가로 스프링 부트는 DefaultFormattingConversionService 를 상속 받은 WebConversionService 를 내부에서 사용한다.
8. 포맷터 적용하기
WebConfig - 수정
-
package hello.typeconverter; import hello.typeconverter.converter.IntegerToStringConverter; import hello.typeconverter.converter.IpPortToStringConverter; import hello.typeconverter.converter.StringToIntegerConverter; import hello.typeconverter.converter.StringToIpPortConverter; import hello.typeconverter.formatter.MyNumberFormatter; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToIpPortConverter()); registry.addConverter(new IpPortToStringConverter()); //MyNumberFormatter() 보다 우선순위가 높기 때문에 주석처리 //우선순위는 converter -> formatter // registry.addConverter(new StringToIntegerConverter()); // registry.addConverter(new IntegerToStringConverter()); //추가 registry.addFormatter(new MyNumberFormatter()); } }
주의 StringToIntegerConverter , IntegerToStringConverter 를 꼭 주석처리 하자.
- MyNumberFormatter 도 숫자 <-> 문자로 변경하기 때문에 둘의 기능이 겹친다.
- 우선순위는 컨버터가 우선하므로 포맷터가 적용되지 않고, 컨버터가 적용된다.
실행
객체 -> 문자
http://localhost:8080/converter-view
- ${number}: 10000
- $: 10,000
- 컨버전 서비스를 적용한 결과 MyNumberFormatter 가 적용되어서 10,000 문자가 출력된 것을 확인할 수 있다.
문자 -> 객체
http://localhost:8080/hello-v2?data=10,000
- MyNumberFormatter : text=10,000, locale=ko_KR
- data = 10000
- “10,000” 이라는 포맷팅 된 문자가 Integer 타입의 숫자 10000으로 정상 변환 된 것을 확인할 수 있다.
9. 스프링이 제공하는 기본 포맷터
-
스프링은 자바에서 기본으로 제공하는 타입들에 대해 수 많은 포맷터를 기본으로 제공한다.
-
IDE에서 Formatter 인터페이스의 구현 클래스를 찾아보면 수 많은 날짜나 시간 관련 포맷터가 제공되는 것을 확인할 수 있다.
-
그런데 포맷터는 기본 형식이 지정되어 있기 때문에, 객체의 각 필드마다 다른 형식으로 포맷을 지정하기는 어렵다.
-
@Data static class Form{ private Integer number; private LocalDateTime localDateTime; }
- 이렇게 있을 때, number, localDateTime 각각 필드마다 다른 형식 포맷 지정이 힘들다는 말임.
-
-
스프링은 이런 문제를 해결하기 위해 애노테이션 기반으로 원하는 형식을 지정해서 사용할 수 있는 매우 유용한 포맷터 두 가지를 기본으로 제공한다.
- @NumberFormat : 숫자 관련 형식 지정 포맷터 사용, NumberFormatAnnotationFormatterFactory
- @DateTimeFormat : 날짜 관련 형식 지정 포맷터 사용, Jsr310DateTimeFormatAnnotationFormatterFactory
FormatterController
-
package hello.typeconverter.controller; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.NumberFormat; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import java.time.LocalDateTime; @Controller public class FormatterController { @GetMapping("/formatter/edit") public String formmaterForm(Model model){ Form form = new Form(); form.setNumber(10000); form.setLocalDateTime(LocalDateTime.now()); model.addAttribute("form", form); return "formatter-form"; } //문자가 들어오지만 Integer, LocalDateTime 으로 바꾼다. 어노테이션으로 패턴을 알기때문 @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 클래스를 보면 각각의 필드에 포맷이 적용되어 있다. (@NumberFormat)
@GetMapping(“/formatter/edit”)
- GET /formatter/edit 요청
- model 에 Form form 을 담아서 반환 (Integer, LocalDateTime 을 가지고 있음)
- @NumberFormat, @DateTimeFormat 를 통해 formatter 가 동작해서 String 으로 변경해서 렌더링!
@PostMapping(“/formatter/edit”)
- POST /formatter/edit 로 String 타입의 데이터를 보낸다. “10,000”, “2023-03-16 22:57:55”
- @ModelAttribute Form form 으로 받을 때 formatter 가 동작
- 각각 Integer, LocalDateTime 으로 변경된다.
formatter-form.html
-
templates/formatter-form.html
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <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> </body> </html>
formatter-view.html
-
templates/formatter-view.html
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> <li>${form.number}: <span th:text="${form.number}" ></span></li> <li>$: <span th:text="$" ></span></li> <li>${form.localDateTime}: <span th:text="${form.localDateTime}" ></span></ li> <li>$: <span th:text="$" ></ span></li> </ul> </body> </html>
참고 > @NumberFormat , @DateTimeFormat 의 자세한 사용법이 궁금한 분들은 다음 링크를 참고하거나 관련 애노테이션을 검색해보자.
댓글남기기