≣ 목차
코드를 작성하다 보면 형 변환을 하는 경우가 엄청 많습니다. Spring TypeConverter는 다양한 데이터 타입 간의 변환을 쉽게 할 수 있게 해주는 중요한 기능입니다. 이번 포스팅은 Spring TypeConverter에 대해서 알아보겠습니다.!!!!
Spring TypeConverter란?
Spring TypeConverter는 다양한 데이터 타입 간의 변환을 지원하는 인터페이스입니다. 이를 통해 데이터 타입 변환을 쉽게 처리할 수 있습니다. 예를 들어, 문자열을 숫자로 변환하거나, 날짜 형식을 변환하는 등의 작업을 쉽게 수행할 수 있습니다.
개념 및 정의
Spring TypeConverter는 주로 두 가지 주요 인터페이스를 사용합니다.
1. Converter<S, T>: S 타입을 T 타입으로 변환합니다.
2. GenericConverter: 복잡한 작업을 지원 및 애노테이션 정보 사용이 가능합니다.
역할 및 기능
Spring TypeConverter의 주요 역할은 데이터 타입 변환입니다. 이를 통해 다음과 같은 기능을 제공합니다.
1. 간단한 데이터 타입 변환: 문자열을 숫자, 날짜 등으로 변환
2. 복잡한 데이터 타입 변환: 사용자 정의 객체 간의 변환
3. 자동 변환: Spring 프레임워크 내부에서 자동으로 변환을 처리
자동 형 변환 예시
@GetMapping("/example-v1")
public String exampleV1(HttpServletRequest request) {
String data = request.getParameter("data");
Integer intValue = Integer.valueOf(data);
System.out.println("intValue = " + intValue);
return "ok";
}
@GetMapping("/example-v2")
public String exampleV2(@RequestParam Integer data) {
System.out.println("data = " + data);
return "ok";
}
쿼리 스트팅으로 10을 전달한다고 가정하면 10은 숫자가 아니고 문자'10' 입니다. 이것을 @RequestParam 을 사용하면 이 문자 10을 Integer 타입의 숫자 10으로 편리하게 받을 수 있습니다. 이것은 스프링 Converter가 중간에서 형 변환을 해주었기 때문입니다.
Spring TypeConverter의 장점
1. 코드의 간결성: 복잡한 변환 로직을 단순화
2. 재사용성: 한 번 작성한 변환기를 여러 곳에서 재사용 가능
3. 유지보수성: 변환 로직을 중앙에서 관리하여 유지보수가 용이
ConversionService란?
ConversionService는 Converter보다 더 광범위한 작업을 처리할 수 있으며, 스프링은 개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용할 수 있는 기능을 제공하는데 이것이 바로 ConversionService입니다.
ConversionService의 장점
Converter 예시
@Test
void StringToInteger() {
StringToIntegerConverter stringToIntegerConverter = new StringToIntegerConverter();
Integer result = stringToIntegerConverter.convert("10");
assertThat(result).isEqualTo(10);
}
@Test
void IntegerToString() {
IntegerToStringConverter integerToStringConverter = new IntegerToStringConverter();
String result = integerToStringConverter.convert(10);
assertThat(result).isEqualTo("10");
}
@Test
void StringToIpPort() {
StringToIpPortConverter stringToIpPortConverter = new StringToIpPortConverter();
IpPort result = stringToIpPortConverter.convert("123.0.0.1:8080");
assertThat(result).isEqualTo(new IpPort("123.0.0.1", 8080));
// 객체를 비교하는건데 왜 ture가 나오나면 @EqualsAndHashCode를 사용했기 때문에
}
@Test
void IpPortToString() {
IpPortToStringConverter ipPortToStringConverter = new IpPortToStringConverter();
String result = ipPortToStringConverter.convert(new IpPort("123.0.0.1", 8080));
assertThat(result).isEqualTo("123.0.0.1:8080");
}
ConversionService 예시
@Test
void conversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
// 등록
conversionService.addConverter(new StringToIntegerConverter());
conversionService.addConverter(new IntegerToStringConverter());
conversionService.addConverter(new IpPortToStringConverter());
conversionService.addConverter(new StringToIpPortConverter());
// 사용
Integer num = conversionService.convert("10", Integer.class);
String str = conversionService.convert(10, String.class);
IpPort ipPort = conversionService.convert("123.0.0.1:8080", IpPort.class);
String ipPortStr = conversionService.convert(new IpPort("123.0.0.1", 8080), String.class);
// 검증
assertThat(num).isEqualTo(10);
assertThat(str).isEqualTo("10");
assertThat(ipPort).isEqualTo(new IpPort("123.0.0.1", 8080)); // 객체를 비교하는건데 왜 ture가 나오나면 @EqualsAndHashCode때문에
assertThat(ipPortStr).isEqualTo("123.0.0.1:8080");
}
첫번째 예시처럼 하나하나 직접 Converter를 찾아서 형 변환하는 것은 우리가 평소에 형 변환하는 것과 크게 차이는 없습니다. 하지만 ConversionService를 사용하면 Spring에 Converter를 한 번에 등록하고 어떤 Converter를 사용해야 하는지 찾거나 고민하지 않고 형 변환를 할 수 있습니다.
ConversionService 사용 예시
@Getter
@EqualsAndHashCode
public class IpPort {
private String ip;
private int port;
public IpPort(String ip, int port) {
this.ip = ip;
this.port = port;
}
}
@Slf4j
public class StringToIpPortConverter implements Converter<String, IpPort> {
@Override
public IpPort convert(String source) {
log.info("StringToIpPortConverter value = {}", source);
String[] split = source.split(":");
String ip = split[0];
int port = Integer.parseInt(split[1]);
return new IpPort(ip, port);
}
}
@Slf4j
public class IpPortToStringConverter implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
String value = source.getIp() + ":" + source.getPort();
log.info("IpPortToStringConverter value = {}", value);
return value;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new IpPortToStringConverter());
registry.addConverter(new StringToIpPortConverter());
}
}
@GetMapping("/example-v3")
public String ipPort(@RequestParam(name = "data") IpPort ipPort) {
return "ok";
}
ConversionService 사용 로그
ConversionService를 사용하면 스프링 내부에서 컨트롤러 호출하기 전에 ConversionService를 이용해 형 변환을 진행하고 해당 컨트롤러를 호출할 때 그 값을 넘겨줍니다.
HTML(Thymeleaf)에 Converter 적용
Controller
@GetMapping("/example-v4")
public String converterView(Model model) {
model.addAttribute("number", 1000);
model.addAttribute("ipPort", new IpPort("123.0.0.1", 8080));
return "converter-view";
}
HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="">
<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>
결과
- ${xxxx} : 일반적인 Thymeleaf 표현식으로, 변수나 속성 값을 직접 삽입합니다. 이 표현식은 Spring Converter를 사용하지 않으며, 값을 있는 그대로 출력합니다.
- ${{xxxx}} : ${} 표현식이랑 차이점이 있다면 자동으로 ConverterSerivice를 사용해서 변환된 결과를 출력해줍니다. 물론 스프링과 통합 되어서 스프링이 제공하는 컨버전 서비스를 사용하므로, 우리가 등록한 컨버터들을 사용할 수 있다
Formatter - 포맷터
문자와 객체 간의 변환을 처리하기 위해 사용되는 인터페이스입니다. Formatter는 두 가지 주요 메서드인 parse와 print를 정의하여 문자열을 객체로 변환하고 객체를 문자열로 변환합니다. 또한, Locale을 지원하여 다국어 및 지역별 형식을 처리할 수 있습니다.
String print(T object, Locale locale) : 객체 -> 문자 변경
T parse(String text, Locale locale) : 문자 -> 객체 변경
Formatter 구현 예시
@Slf4j
public class NumberFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text={}, locale={}", text, locale);
NumberFormat format = NumberFormat.getInstance(locale);
return format.parse(text);
}
@Override
public String print(Number object, Locale locale) {
log.info("object={}, locale={}", object, locale);
NumberFormat instance = NumberFormat.getInstance(locale);
return instance.format(object);
}
}
class NumberFormatterTest {
NumberFormatter formatter = new NumberFormatter();
@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");
}
}
포맷터는 기본적으로 객체를 문자로 변경 및 문자를 객체로 변경하는 기능들을 모두 수행하므로, print랑 parse를 모두 구현해야 합니다.
Formatter를 ConversionService에 등록

Formatter를 ConversionService등록 구현체
@Test
void formattingConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
FormattingConversionService conversionService1 = new FormattingConversionService();
//컨버터 등록
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
//포맷터 등록
conversionService.addFormatter(new NumberFormatter());
//컨버터 사용
IpPort ipPort = conversionService.convert("123.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("123.0.0.1", 8080));
//포맷터 사용
assertThat(conversionService.convert(1000, String.class)).isEqualTo("1,000");
assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L);
}
Formatter와 애노테이션으로 다양한 데이터 형식 지정
@GetMapping("/example-v6")
public String formatterForm(Model model) {
Form form = new Form();
form.setNumber(100000000);
form.setLocalDateTime(LocalDateTime.now());
model.addAttribute("form", form);
return "formatter-form";
}
@PostMapping("/example-v6")
public String formatterEdit(@ModelAttribute Form form) {
return "formatter-view";
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
static class Form {
@NumberFormat(pattern = "###,###")
private Integer number;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
}
결과

참고자료
https://docs.spring.io/spring-framework/reference/core/validation/convert.html
'Spring' 카테고리의 다른 글
[Spring] Filter(필터) VS Interceptor(인터셉터) 정리 및 차이 (1) | 2024.08.01 |
---|---|
[Spring + Servlet] 파일 업로드 (1) | 2024.07.31 |
[Spring] 스프링이 제공하는 ExceptionResolver에 대하여 (0) | 2024.07.08 |
[Spring] 스프링이 제공하는 HandlerExceptionResolver에 대해서(ExceptionHandlerExceptionResolver) (0) | 2024.07.04 |
[OAuth2.0 + JWT + Spring] 스프링 시큐리티로 OAuth2.0 로그인 구현(카카오) (0) | 2024.07.01 |