Spring Converter
얼마전 포스팅했던 Joda Time에 대해 사내발표를 진행했는데... 사내 프로젝트 내에서 Date타입으로 선언된 필드들을 Joda Time으로 변경하자는 의견이 나왔고, 다른 개발자들이 별다른 설정이나 추가코드를 작성할 필요없이 Joda Time 클래스를 사용할 수 있게 하라는 지시가 내려왔다. 이렇게까지 일을 벌일(?) 생각은 없었는데 그 작업을 맡게되었고 일단 날짜 데이터를 화면에서 보내면 LocalDate(혹은 LocalDateTime)로 자동으로 받을 수 있게끔 작업하는것부터 시작이 됐어야했다. 그 작업을 포스팅으로 남겨두려한다.
1. Spring Data Binding
일단 Spring 컨트롤러는 매개변수로 변수를 넣어놓으면 알아서 해당 타입으로 값을 갖다 쓸 수있게 되어있다.
@RequestMapping("/hello")
public String hello(int page){
return "hello";
}
인자들이 여러개가 될경우 Command 객체를 인자로 받게하면 해당 객체로 받아주기도 한다.
@RequestMapping("/hello")
public String hello(Command command){
return "hello";
}
public static class Command{
private int page;
public void setPage(int page){
this.page = page;
}
public int getPage(){
return this.page;
}
}
별 생각없이 잘 쓰고있긴한데 이제와서 생각해보면 참 신통방통한 일이 아닐 수 없다. 스프링 없이 서블릿 기반으로 게시판만 하나 만들어봤으면 알 수 있겠지만 화면에서 전달되는 데이터는 기본적으로 String 타입이기 때문이다.
HttpServletRequest 의 getParameter() 메서드는 리턴타입이 String 인 이유이다. 결국은 어디선가 형변환을 해주는곳이 있다는 얘기가 된다.
2. Converter
날짜 형태로 넘어오는 값 역시 형태가 어찌됐든 문자열로 넘어올것이고, 그 문자열을 LocalDate 타입으로 변환해주는 코드를 작성해야한다는 의미가 된다. 그 역할을 해주는게 Converter이다.
Spring core 프로젝트에 있는 Converter<S, T> 인터페이스를 구현해주면 된다. S는 input 타입이고 T는 return 타입이다.
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S var1);
}
구현은 어려운게 없다. 그저 타입변환만 해주면 되니..
/**
* Created by LichKing on 2017. 3. 12..
*/
public class StringToLocalDateConverter implements Converter<String, LocalDate> {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
@Override
public LocalDate convert(String s) {
return LocalDate.parse(s, DATE_TIME_FORMATTER);
}
}
parse() 메서드는 인자를 1개만 받는 메서드도있는데 그 경우엔 기본 패턴이 yyyy-MM-dd 이다. 기본 형태로 넘겨준다면 따로 DateTimeFormatter 객체를 만들필요는 없다.
인자에대한 empty 체크 정도는 해줘야 할것 같다.
@Override
public LocalDate convert(String s) {
if(s == null || s.isEmpty()){
return null;
}
return LocalDate.parse(s, DATE_TIME_FORMATTER);
}
값이 없을때 기본날짜를 넘겨주거나 Optional을 넘겨주거나 null을 넘겨줄지는 정책을 정해서 가면 될 것 같다.
Converter는 만들었으니 이제 이걸 등록해주자. ConverterFactory 빈을 만들어줘야한다. 현재 XML 기반의 설정파일을 사용하고있으므로 XML 설정법으로 이어가겠다.
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.yong.spring.binding.StringToLocalDateConverter"/>
</set>
</property>
</bean>
빈을 생성하고
<mvc:annotation-driven conversion-service="conversionService">
서비스를 등록해주면 Converter를 통해 LocalDate로 넘어오는걸 확인할 수 있다.
@RequestMapping("/hello")
public String hello(Command command){
return "hello";
}
public static class Command{
private LocalDate date;
public void setDate(LocalDate date){
this.date = date;
}
public LocalDate getDate(){
return this.date;
}
}
#2017.04.09 추가
xml이 아닌 java 기반의 설정을 하고있다면 config 파일에 이렇게 추가해주면 된다.
@Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new Converter<String, LocalDate>(){
@Override
public LocalDate convert(String s) {
return null;
}
});
}
나는 현재 xml기반으로 사용하고있고, 자바 설정 예제를 위해 간략히 짠거라 일단 익명 클래스 방식으로 했는데..java8을 사용한다면 xml로 설정할때처럼 따로 파일 만들필요없이 람다로 만들어놓으면 관리하기도 편할것 같기도하다(변환 코드가 짧다는 가정하에..).
@Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter((String s) -> LocalDate.parse(s));
}
더 줄여서 메서드 레퍼런스로 만들어버리자
@Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter((Converter<String, Object>) LocalDate::parse);
}