Spring
[Spring] Converter와 Formatter
정 효
2024. 7. 8. 21:47
Converter
- 객체와 객체간의 타입 변환 (범용)
- 직접 타입 변환 시
@RestController
public class HelloController {
@GetMapping("/hello-v1")
public String helloV1(HttpServletRequest request) {
String data = request.getParameter("data");
Integer intValue = Integer.valueOf(data);
System.out.println("intValue = " + intValue);
return "ok";
}
}
- Spring이 제공하는
ConversionService
이용
@GetMapping("/hello-v2")
public String helloV2(@RequestParam Integer data) {
System.out.println("data = " + data);
return "ok";
}
-> @ModelAttribute
나 @PathVariable
에도 적용됨
Converter
인터페이스를 구현하여 직접 Converter 생성 가능
package org.springframework.core.convert.converter;
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
- Integer to String Converter
@Slf4j
public class IntegerToStringConverter implements Converter<Integer, String> {
@Override
public String convert(Integer source) {
log.info("convert source={}", source);
return String.valueOf(source);
}
}
- Strign to Integer Converter
@Slf4j
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
log.info("convert source={}", source);
return Integer.valueOf(source);
}
}
- IpPort
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class IpPort {
private String ip;
private int port;
}
- IpPort to String Converter
@Slf4j
public class IpPortToStringConverter implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
log.info("converter source={}", source);
return source.getIp() + ":" + source.getPort();
}
}
- String to IpPort Converter
@Slf4j
public class StringToIpPortConverter implements Converter<String, IpPort> {
@Override
public IpPort convert(String source) {
log.info("convert source={}", source);
//"127.0.0.1:8080"
String[] split = source.split(":");
String ip = split[0];
int port = Integer.parseInt(split[1]);
return new IpPort(ip, port);
}
}
- 직접 Converter 호출
public class ConverterTest {
@Test
void stringToInteger() {
StringToIntegerConverter converter = new StringToIntegerConverter();
Integer result = converter.convert("10");
assertThat(result).isEqualTo(10);
}
@Test
void integerToString() {
IntegerToStringConverter converter = new IntegerToStringConverter();
String result = converter.convert(10);
assertThat(result).isEqualTo("10");
}
}
- ConversionService 등록하여 사용
@Test
void conversionService() {
//등록
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToIntegerConverter());
conversionService.addConverter(new IntegerToStringConverter());
assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
assertThat(conversionService.convert(10, String.class)).isEqualTo("10");
}
- Spring에 등록하여 사용
@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());
}
}
- @RequestParam, @ModelAttribute, @PathVariable 등에 자동 적용
- Thymeleaf에서 사용
@Slf4j
@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";
}
@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";
}
@PostMapping("/converter/edit")
public String converterEdit(@ModelAttribute Form form, Model model) {
IpPort ipPort = form.getIpPort();
model.addAttribute("ipPort", ipPort);
return "converter-view";
}
@Data
static class Form {
private IpPort ipPort;
public Form(IpPort ipPort) {
this.ipPort = ipPort;
}
}
}
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 사용 시 Converter 자동 적용
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>${{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>
- ${{}} 안에 사용 시 Converter 적용
Formatter
- 객체와 문자간의 변환 + 현지화 기능
Converter
의 특별한 버전Formatter
인터페이스 구현하여 사용
package org.springframework.format;
@FunctionalInterface
public interface Printer<T> {
String print(T object, Locale locale);
}
@FunctionalInterface
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
@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);
}
}
- 직접 호출하여 사용
MyNumberFormatter formatter = new MyNumberFormatter();
@Test
void parse() throws ParseException {
Number result = formatter.parse("1,000", Locale.KOREA);
assertThat(result).isEqualTo(1000L);
}
@Test
void print() {
String result = formatter.print(1000, Locale.KOREA);
assertThat(result).isEqualTo("1,000");
}
FormattingConversionService
등록하여 사용
@Test
void formattingConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); =
conversionService.addFormatter(new MyNumberFormatter());
//포맷터 사용
assertThat(conversionService.convert(1000, String.class)).isEqualTo("1,000");
assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L);
}
- Spring에 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new MyNumberFormatter());
}
}
- Spring이 제공하는 annotation 기반 Formatter
@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;
}
}
- @NumberFormat: 문자 <-> 숫자 변환
- @DateTimeFormat: 문자 <-> 날짜 변환
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
<!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>${{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>
</body>
</html>
- 중괄호 하나일 땐 `Formatter`에 설정된 패턴을 확인하여 Integer, LocalDateTime에 맞게 변환하여 출력
- 두개일 땐 thymeleaf에서 `Formatter` 적용하여 지정한 패턴으로 출력