스프링 웹프로젝트를 하다보면 문자를 숫자로 변환하거나, 반대로 숫자를 문자로 등 타입을 변화해야하는 경우가 많다.
스프링 기능인 타입 컨버터가 이를 지원한다.
HTTP 요청 파라미터는 모두 문자로 처리된다.
숫자가 필요한 경우 타입을 변환하는 과정을 거친다.
@GetMapping("/hello-v1")
public String helloV1(HttpServletRequest request) {
//요청 파라미터는 모두 문자로 처리된다.
String data = request.getParameter("data");
//자바에서 다른 타입으로 변환할 때 방법
Integer intValue = Integer.valueOf(data);
System.out.println(intValue);
return "WOW";
}
스프링이 제공하는 @RequestParam을 사용할 경우 자체적으로 변환해준다.
그 외 @ModelAttribute, @PathVariable도 동일하다.
@GetMapping("/hello-v2")
public String helloV2(@RequestParam Integer data) {
//스프링이 중간에서 타입을 변환해줌
System.out.println(data);
return "WOW";
}
그 외 추가적으로 다양한 타입으로 변환을 원할 경우엔 스프링에서 제공하는 컨버터 인터페이스를 구현해서 사용하면 된다.
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
용도에 따라 다양한 방시그이 타입 컨버터가 존재한다.
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#core-convert
이제 직접 코드를 통해 타입 컨버터를 구현해본다.
@Slf4j
public class IntegerToStringConverter implements Converter<Integer, String> {
@Override
public String convert(Integer source) {
log.info("convert source={}", source);
return String.valueOf(source);
}
}
@Slf4j
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
log.info("convert source={}", source);
return Integer.valueOf(source);
}
}
테스트 코드를 통해 정상동작되는지 확인해본다.
@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");
}
더 이해하기 위해 IP와 Port를 입력받아 IpPort 객체로 변환하는 컨버터를 만들어본다.
127.0.0.1:8080 -> 127.0.0.1 / 8080
@Getter
@EqualsAndHashCode //모든 필드에 equals(), hashcode() 생성됨
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("convert source={}", source);
//127.0.0.1:8080
String[] split = source.split(":");
//127.0.0.1, 8080
String ip = split[0];
//127.0.0.1
int port = Integer.parseInt(split[1]);
//8080
return new IpPort(ip, port);
}
}
@Slf4j
public class IpPortToStringConverter implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
log.info("convert source={}", source);
return source.getIp() + ":" + source.getPort();
//127.0.0.1:8080
}
}
위와 같이 타입마다 컨터버를 직접 찾아 변환하는 것은 매우매우 불편하다!
그래서 스프링은 개별 컨버터를 모아두고 그것들을 묶어서 사용할 수 있는 기능을 제공하는데.. ConversionService이다.
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);
}
테스트로 확인해본다.
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class ConversionServiceTest {
@Test
void conversionService() {
//등록
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
//사용
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
}
}
앞서 테스트를 보듯이 컨버터를 등록하기 위해서 StringToIpPortConverter 같이 타입 컨버터를 정확히 알아야 한다.
하지만 사용자 입장에서는 매번 타입 컨버터를 몰라도 된다. 그래서 타입 컨버터는 컨버전 서비스 내부에 숨어있다.
따라서 타입을 변환을 원하는 사용자는 컨버전 서비스 인터페이스에만 의존하면 된다.
컨버전 서비스를 등록하는 부분과 사용하는 부분을 분리하고 의존관계 주입을 사용해야한다.
인터페이스로 분리함해 컨버터를 사용하는 사용자와 컨버터를 등록하고 관리하는 사용자의 관심사를 명확하게 분리했다.
IpPort value = conversionService.convert("127.0.0.1:8080", IpPort.class);
이제 웹 애플리케이션에서 Converter를 적용해본다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
//추가하고 싶은 컨버터 등록
registry.addConverter(new StringToIpPortConverter());
registry.addConverter(new IpPortToStringConverter());
}
}
http://localhost:8080/hello-v2?ipPort=127.0.0.1:8080
@GetMapping("/hello-v2")
public String helloV2(@RequestParam IpPort data) {
//스프링이 중간에서 타입을 변환해줌
System.out.println("IP = " + data.getIp()); //127.0.0.1
System.out.println("IP = " + data.getPort()); //8080
return "WOW";
}
뷰 템플릿에서 컨버터 사용되는 것을 보자
@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";
}
타입리프에서 ${{...}}를 사용하면 자동으로 컨버전 서비스를 사용해 변환된 결과를 출력한다.
폼인 경우 th:field가 자동으로 컨버전 서비스를 적용해준다.
<!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>
객체를 특정한 포멧에 맞추어 문자로 출력하거나 그 반대로 역할을 하는 것을 Formatter라고 한다.
Converter는 객체 -> 객체
Formatter는 문자에 특화되어 객체 -> 문자 + 현지화(Locale)
@Slf4j
public class MyNumberFormatter 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);
return NumberFormat.getInstance(locale).format(object);
}
}
MyNumberFormatter formatter = new MyNumberFormatter();
@Test
void parse() throws ParseException {
Number result = formatter.parse("1,000", Locale.KOREA);
Assertions.assertThat(result).isEqualTo(1000L);
}
@Test
void print() {
String result = formatter.print(1000, Locale.KOREA);
Assertions.assertThat(result).isEqualTo("1,000");
}
스프링에서 기본으로 제공하는 타입 포맷터가 있다.
@Data
static class Form {
@NumberFormat(pattern = "###,###")
private Integer number;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
}
김영한, 스프링 MVC 2편
'🖥️ Back > Spring' 카테고리의 다른 글
JDBC (0) | 2025.07.10 |
---|---|
김영한 스프링 MVC 2편 - 파일 업로드 (2) | 2025.07.10 |
김영한 스프링 MVC 2편 - API 예외 처리 (0) | 2025.07.07 |
김영한 스프링 MVC 2편 - 예외 처리와 오류 페이지 (0) | 2025.07.06 |
김영한 스프링 MVC 2편 - 로그인, 필터, 인터셉터 (0) | 2025.07.06 |