스프링 부트 - 타입 컨버터, 포맷터

SeungTaek·2021년 8월 18일
1
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
틀린 내용이 있을 수 있습니다.

📒스프링 타입 컨버터

  • 웹 개발을 하다보면 문자를 숫자로 변환하거나, 숫자를 문자로 변환해야 하는 일이 상당히 많다.
  • HTTP 요청 파라미터는 모두 문자(String)으로 처리되기 때문에 자바에서 다른 타입으로 변환하는 과정을 거쳐야 했다.
  • 근데 @RequestParam을 사용한다면? Integer타입으로 받을 수 있다는걸 이미 알고 있을것이다. (@ModelAttribute, @PathVariable도 마찬가지)

Q) HTTP 요청 파라미터는 모두 String인데, 어떻게 Integer타입으로 받을 수 있는거지?

  • A) 스프링이 중간에서 타입을 변환해주기 때문이다.
  • 개발자는 추가적인 타입 변환이 필요하면 컨버터 인터페이스를 구현해서 등록하면 된다.

📌 컨버터 인터페이스 구현하기

  • 127.0.0.1:8080String을 사용자가 만든 클래스인 IpPort로 변환하게 구현을 할것이다.

🎈클래스 만들기

public class IpPort {
 	private String ip;
 	private int port;
 	public IpPort(String ip, int port) {
 		this.ip = ip;
 		this.port = port;
	}
}

🎈구현하기

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);
 	}
}

🎈등록하기.. 는 아래에서 계속


📌 컨버전 서비스 - ConversionService

  • 우리가 구현한 컨버터들을 등록하고 사용할 수 있게 해주는 서비스이다.
  • 아래 코드를 통해 등록 예시와 사용 예시를 보자.
//등록
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToIpPortConverter());

//사용
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
  • 코드를 보면 알겠지만, 등록할 때에는 우리가 구현한 클래스의 정확한 이름을 알아야 한다.
  • 하지만 사용할 때에는 바꿀 데이터와 반환받고 싶은 클래스를 입력하면 등록된 컨버터들 중에서 가장 알맞는 컨버터를 통해 리턴해준다.

아! 그러면 등록하는 인터페이스와 사용하는 인터페이스를 분리하면, 사용할땐 컨버터 이름을 몰라도, 알아서 변환시켜 주는구나!

  • 컨버팅을 사용할때마다 저런 코드를 쓸 순 없으니, 아래에 있는것처럼 스프링에 Converter을 등록해서 사용하자.

🎈스프링에 Converter 등록하기

@Configuration
public class WebConfig implements WebMvcConfigurer {
 	@Override
 	public void addFormatters(FormatterRegistry registry) {
 		registry.addConverter(new StringToIpPortConverter());
 	}
}
  • 이렇게 등록만 해두면 @RequestParam, @ModelAttribute, @PathVariable에서 알아서 컨버트해준다.

🎈 Converter 동작 확인하기

//잘 동작하는지 확인. "127.0.0.1:8080"을 RequestParam으로 받아보자.
@GetMapping("/myIp")
public String ipPort(@RequestParam IpPort data) {
 	System.out.println("ipPort IP = " + ipPort.getIp());
 	System.out.println("ipPort PORT = " + ipPort.getPort());
 	return "ok";
}
  • 스프링 내부에는 수 많은 기본 컨버터들이 등록되어 있다. 우리가 새롭게 컨버터를 추가하면, 추가한 컨버터가 높은 우선순위를 가진다.

📌 포맷터 - Formatter란?

  • 범용 기능 컨버터는 이미 스프링에서 등록까지 다 되어있다.

  • 개발자 입장에서는 객체를 특정한 포멧에 맞추어 문자로 출력하는 등의 기능이 필요할 수 있다.(예를 들어 숫자 1000을 문자 1,000으로 변환, 날짜 객체를 문자인 yy-MM와 같이 출력하는 등)

  • 이런 기능을 포맷터(Formatter)라고 한다. 포맷터는 컨버터의 특벌한 버전이라고 이해하면 된다.

  • Converter vs Formatter

    • Converter: 범용(객체 -> 객체로 변환)
    • Formatter: 문자에 특화(객체 -> 문자, 문자->객체) + 현지화(Locale)

🎈 포맷터 만들기

  • 숫자 1000을 문자 1,000으로 만드는 포맷을 만들고 적용해보자.
public class MyNumberFormatter implements Formatter<Number> {
    @Override
	public Number parse(String text, Locale locale) throws ParseException {
 		log.info("text={}, locale={}", text, locale);
        //쉼표 넣는 기능은 NumberFormat에 구현되어있다.
 		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);
 	}
}

🎈포맷터 등록하기

DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addFormatter(new MyNumberFormatter());
  • 또는 Configuration에 등록하기(단, 우선 순위는 컨버터가 더 우선이다.)
@Configuration
public class WebConfig implements WebMvcConfigurer {
 	@Override
 	public void addFormatters(FormatterRegistry registry) {
		 registry.addFormatter(new MyNumberFormatter());
 	}
}

🎈 포맷터 사용해보기

  • 만약 http://localhost:8080/test?data=1,000을 요청한다 해보자.
@GetMapping("/test")
public String ipPort(@RequestParam Integer data) {
	log.info("data={}", data);
 	return "ok";
}
  • 로그를 확인하면 정상적으로 Integer1000으로 받은것을 알 수 있다.

📌스프링이 제공하는 기본 포맷터

  • 스프링에선 수 많은 포맷터를 기본으로 제공한다.
  • 애노테이션 기반으로 원하는 형식을 지정해서 사용할 수 있는 매우 유용한 포맷터 두 가지가 있다.
    • @NumberFormat: 숫자 관련 형식 지정 포맷터 사용
    • @DateTimeFormat: 날짜 관련 형식 지정 포맷터 사용
  • 예를 들어보자

🎈 Form 생성

@Data
static class Form {
 	@NumberFormat(pattern = "###,###")
	private Integer number;
 	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
 	private LocalDateTime localDateTime;
}

🎈 컨트롤러

@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";
 	}

🎈 타임리프

  • formatter-form.html
<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>
  • formatter-view.html
${{form.number}}: <span th:text="${{form.number}}" ></span>
${{form.localDateTime}}: <span th:text="${{form.localDateTime}}" ></
span>

🎈 결과

${{form.number}}: 10,000
${{form.localDateTime}}: 2021-01-01 00:00:00

🎈 정리

  • 타임리프의 경우 컨버트나 포맷트된 값을 보려면 th:text="${{ ~ }}"을 사용해야 한다.
  • model.addAttribute에 포맷팅된 값이 잘 들어간것을 확인 할 수 있다.
  • PostMappting에서 @ModelAttribute Form form로 포맷팅된 값을 잘 받은것을 알 수 있다.

❌주의❌

  • 메시지 컨버터(HttpMessageConverter)에는 컨버전 서비스가 적용되지 않는다.

  • 예를 들어서 JSON을 객체로 변환하는 메시지 컨버터는 내부에서 Jackson 같은
    라이브러리를 사용한다.

  • JSON 결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶으면 해당 라이브러리가 제공하는 설정을 통해서 포맷을 지정해야 한다.

  • 결과적으로 이것은 컨버전 서비스와 전혀 관계가 없다.

  • 컨버전 서비스는 @RequestParam , @ModelAttribute , @PathVariable , 뷰 템플릿 등에서 사용할 수 있다.


인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요

profile
I Think So!

0개의 댓글