@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";
}
@GetMapping("hello-v2") //@RequestParam을 사용하면 스프링이 중간에서 타입 변환을 해줌
public String helloV2(@RequestParam Integer data) {
System.out.println("data = " + data);
return "ok";
}
}
Http 쿼리 스트링으로 전달하는 data=10은 숫자 10이 아니라 문자 10이다.
이렇게 @RequestParam으로 문자 10을 숫자 10으로 스프링이 중간에서 타입 변환을 해주어서 편리하게 받을 수 있다.
@PathVariable의 /users/{userid}에서도 userid부분은 문자다. 이것도 스프링이 타입 변환을 중간에서 해주는 것이다.
이렇게 문자 -> 숫자만이 아니라 숫자 -> 문자, Boolean -> 숫자도 가능하다.
스프링의 타입 변환 적용 예
- 스프링 MVC 요청 파라미터
- @RequestParam, @ModelAttribute, @PathVariable
- @Value등으로 YML 정보 읽기
- XML에 넣은 스프링 빈 정보를 변환
- 뷰를 렌더링 할 때
개발자가 새로운 타입을 만들어서 변환하고 싶을 때도 스프링의 확장 가능한 컨버터 인터페이스를 사용하면 된다. 컨버터 인터페이스를 구현해서 등록하면 된다.
@Slf4j
public class StringToIntegerConvertor implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
log.info("convert source = {}", source);
Integer integer = Integer.valueOf(source);
return integer;
}
}
@Slf4j
public class IntegerToStringConvertor implements Converter<Integer, String> {
@Override
public String convert(Integer source) {
log.info("convertor source = {}", source);
return String.valueOf(source);
}
}
@Slf4j
public class StringToIpPortConvertor implements Converter<String, IpPort> {
@Override
public IpPort convert(String source) {
log.info("convertor 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);
}
}
@Slf4j
public class IpPortToStringConvertor implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
log.info("convertor source = {}", source);
//IpPort 객체 -> "127.0.0.1:8080"
return source.getIp() + ":" + source.getPort();
}
}
이렇게 간단하게 구현할 수 있다.
이렇게 만든 Convertor들 ConversionSevice에서 모아둘 수 있다.
ConversionSevice 인터페이스는 컨버팅이 가능한지 확인하는 기능과 컨버팅 기능을 제공한다.
스프링에 Convertor 적용
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToIntegerConvertor());
registry.addConverter(new IntegerToStringConvertor());
registry.addConverter(new StringToIpPortConvertor());
registry.addConverter(new IpPortToStringConvertor());
}
}
이렇게 추가한 컨버터가 스프링이 기본 제공하는 컨버터보다 우선순위를 가진다.
@GetMapping("/ip-port")
public String ipPort(@RequestParam IpPort ipPort) {
System.out.println("ipPort IP = " + ipPort.getIp());
System.out.println("ipPort PORT = " + ipPort.getPort());
return "ok";
}
결과
2024-01-15T13:24:33.664+09:00 INFO 33604 --- [nio-8080-exec-3] h.t.convertor.StringToIpPortConvertor : convertor source = 127.0.0.1:8080
ipPort IP = 127.0.0.1
ipPort PORT = 8080
@RequestParam에서 ArgumentResolver인 RequestParamMethodArgumentResolve에서 ConvertService를 호출해서 타입을 변환한다.
뷰 템플릿에 Convertor 적용
타임리프는 렌더링 시에 컨버터를 적요해서 렌더링하는 방법을 지원한다.
<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>
${{number}}, ${{ipPort}}처럼 중괄호가 두개 쳐져있는 것만 컨버터가 동작한다.
결과
${number}: 1000000
${{number}}: 1000000
${ipPort}: hello.typeconvertor.type.IpPort@59cb0946
${{ipPort}}: 127.0.0.1:8080
${number}는 스프링에서 알아서 convert해준 것이다.
Form에 적용하기'
<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와 th:value 모두 중괄호를 두개 사용했다.
결과
이렇게 th:field는 알아서 컨버트해주고, th:value는 해주지 않는다.
th:value에서 중괄호를 두번 사용하면 컨버트해준다.
Formatter
Converter는 입력과 출력 타입에 제한이 없는 범용 타입 변환 기능을 제공한다.
- 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);
//"1,000" -> 1000
NumberFormat format = NumberFormat.getInstance(locale); //locale 정보를 사용해서 나라마다 다른 숫자 포맷 만들기
Number parse = format.parse(text);
return parse;
}
@Override
public String print(Number object, Locale locale) {
log.info("object = {}, locale = {}", object, locale);
// 1000 -> "1,000"
NumberFormat instance = NumberFormat.getInstance(locale);
String format = instance.format(object);
return format;
}
}
@Test
void formattingConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
//컨버터 등록
conversionService.addConverter(new StringToIpPortConvertor());
conversionService.addConverter(new IpPortToStringConvertor());
//포맷터 등록
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(1000);
}
DefaultFormattingConversionService는 ConversionService관련 기능을 상속 받기 때문에 결과적으로 컨버터도 포맷터도 모두 사용 가능하다!
스프링에서 사용하기
컨버터와 똑같이 WebConfig에 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
//우선순위때문에 주석처리
// registry.addConverter(new StringToIntegerConvertor());
// registry.addConverter(new IntegerToStringConvertor());
registry.addConverter(new StringToIpPortConvertor());
registry.addConverter(new IpPortToStringConvertor());
//추가
registry.addFormatter(new MyNumberFormatter());
}
}
결과
${number}: 1000000
${{number}}: 1,000,000
${ipPort}: hello.typeconvertor.type.IpPort@59cb0946
${{ipPort}}: 127.0.0.1:8080
쿼리스트링에서 10,000으로 입력된 것도 10000으로 바뀌는 것을 확인할 수 있었다.
스프링에서 제공하는 기본 포맷터
@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으로 형식을 구체적으로 지정 가능!
Form에서 적용하는 것은 앞에서 설명한 th:field, th:value 그리고 중괄호 두개와 동일하다.
'Spring' 카테고리의 다른 글
JDBC (0) | 2024.01.31 |
---|---|
Spring Toy Project-1 (3) | 2024.01.31 |
API 예외 처리 (0) | 2024.01.13 |
예외 처리, 오류 페이지 (1) | 2024.01.11 |
스프링 인터셉터 (1) | 2024.01.09 |