on
데이터 바인딩 추상화 : Converter와 Formatter
스프링 프레임워크 핵심기술을 공부하고 정리하는 포스트입니다.
데이터 바인딩 추상화 : Converter와 Formatter
PropertyEditor는 Object-String간의 변환만 가능하다. 이러한 단점을 해결하기 위해 생긴 인터페이스가 바로 Converter와 Formatter이다.
Converter
Converter는 A 타입을 B 타입으로 변환 할 수 있는 좀 더 일반적인 데이터 바인딩이 가능한 변환기이다. PropertyEditor와 다르게 상태 정보를 가지고 있지 않기 때문에 Stateless하다. 즉 ThreadSafe한 것이다.
이제 Converter를 어떻게 사용하는지 살펴보자.
package me.gracenam.demospring51;
import org.springframework.core.convert.converter.Converter;
public class EventConverter{
public static class StringToEventConverter implements Converter<String, Event> {
@Override
public Event convert(String source) {
return new Event(Integer.parseInt(source));
}
}
public static class EventToStringConverter implements Converter<Event, String> {
@Override
public String convert(Event source) {
return source.getId().toString();
}
}
}
EventConverter라는 클래스를 만들었다. Converter는 Converter
라는 인터페이스를 구현하면 되는데 generic type으로 두 가지를 받는다. 하나는 source고 하나는 target이다.
Converter<Source, Target>
의 형태를 가지고 있는데, Source를 Target으로 변환하겠다는 인터페이스이다.
StringToEventConverter는 String(source)을 Event(target)로 변환하는 클래스이고, EventToStringConverter은 Event를 String으로 변환하는 클래스이다.
둘 모두 메소드는 convert()
메소드 하나 뿐이고, 입력을 받아서 변환하는 것이 끝이다.
이렇게 작성한 두 클래스가 PropertyEditor와 같은 역할을 하는 것이고, 이 클래스들은 얼마든지 빈으로 등록해서 사용할 수 있다. 왜냐하면 상태정보를 저장하고 있지 않기 때문이다.
이렇게 작성한 것을 어떻게 등록을 해서 사용해야 할까?
ConverterRegistry라는 것에 등록을 해서 사용을 해야 한다. 하지만 ConverterRegistry
라는 인터페이스를 직접 등록해서 쓸 일은 없다.
package me.gracenam.demospring51;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EventConverter.StringToEventConverter());
}
}
스프링 부트 없이 스프링 MVC를 사용한다면 WebConfig와 같은 Web용 Configuration 클래스를 만들고 WebMvcConfigurer
를 구현한다. 그리고 addFormatters
를 Override하면 registry에 addConverter 할 수 있는 메서드가 있다. 여기에 Converter를 등록해주면 된다.
이렇게 등록해주면 스프링 MVC 설정에 넣어준 Converter가 모든 Controller에서 동작을 하게 된다.
package me.gracenam.demospring51;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EventController {
@GetMapping("/event/{event}")
public String getEvent(@PathVariable Event event) {
System.out.println(event);
return event.getId().toString();
}
}
테스트 코드를 실행하여 정상적으로 동작이 되는지 확인할 수 있다.
package me.gracenam.demospring51;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void getTest() throws Exception {
mockMvc.perform(get("/event/1"))
.andExpect(status().isOk())
.andExpect(content().string("1"));
}
}
Formatter
Formatter는 Converter보다 조금 더 Web쪽에 특화된 인터페이스이다. Formatter의 경우 처리할 타입을 하나 주고 두 개의 메서드를 구현한다. 하나는 문자를 객체로, 다른 하나는 객체를 문자로 변환하는 메서드이다. 제네릭으로 하나의 인자만 받는 이유는 Object와 String 간의 변환에 사용되기 때문에 다른 하나의 인자가 String으로 고정되어있기 때문이다.
한 가지 특징으로 locale 정보를 기반으로 바꿀 수 있는 것인데 이는 MassageSource를 공부할 때 나온 다국화와 관련된 기능이다.
package me.gracenam.demospring51;
import org.springframework.format.Formatter;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.Locale;
@Component
public class EventFormatter implements Formatter<Event> {
@Override
public Event parse(String text, Locale locale) throws ParseException {
return new Event(Integer.parseInt(text));
}
@Override
public String print(Event object, Locale locale) {
return object.getId().toString();
}
}
Formatter 역시 ThreadSafe하기 때문에 빈으로 등록하거나 다른 빈을 주입받아서 사용할 수 있다.
사용방법은 Converter와 동일하게 Configuration 클래스를 활용하여 사용하면 된다.
package me.gracenam.demospring51;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new EventFormatter());
}
}
ConversionService
앞서 PropertyEditor를 사용할 때는 데이터 바인더를 통해서 이용했다면 Converter와 Formatter를 사용할 때는 ConversionService를 이용하게 된다.
예제에서 살펴봤던 인터페이스를 통해서 등록되는 Converter와 Formatter는 모두 ConversionService에 등록되어 변환하는 작업이 이뤄진 것이다.
ConversionService는 스프링 MVC, 스프링 Xml 빈 설정파일, SpEL에서 사용한다.
스프링이 제공하는 ConversionService 구현체 중에 DefaultFormattionConversionService라는 것이 있는데, ConversionService 타입으로 DefaultFormattionConversionService 클래스가 자주 사용이 된다.
이 클래스는 FormatterRegistry와 ConversionService 두 가지 인터페이스를 모두 구현했기 때문에 두 가지 기능의 역할을 한다. 그 외에도 기본적인 Converter와 Formatter 등록을 해준다.
DefaultFormattionConversionService의 구조를 살펴보면 알 수 있듯이 FormatterRegistry가 ConverterRegistry를 상속받고 있다. Converter는 ConverterRegistry가 필요하고 Formatter는 FormatterRegistry가 필요한데 FormatterRegistry에서 Converter를 쓸 수 있었던 이유가 바로 이 때문이다.
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ConversionService conversionService;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(conversionService.getClass());
}
}
DefaultFormattionConversionService가 ConversionService의 역할도 한다고 했으니 이를 확인해보기 위해 AppRunner를 만들어서 실행해보자.
생각과 달리 DefaultFormattionConversionService가 아닌 WebConversionService가 출력되었다. 왜 그런것일까?
WebConversionService는 스프링 부트가 제공해주는 서비스이다. 이 클래스는 DefaultFormattionConversionService를 상속해서 만든 것으로 조금 더 많은 기능을 가지고 있다.
WebConversionService (Spring Boot)
스프링 부트를 사용할 경우 WebConversionService가 Formatter와 Converter를 자동으로 등록해준다. 즉, WebConfig와 같은 클래스 파일을 만들어서 스프링 웹 MVC 설정을 할 필요가 없다는 뜻이다.
Converter나 Formatter가 빈으로 등록이 되어 있다면 스프링 부트가 자동으로 ConversionService에 등록을 해준다.
package me.gracenam.demospring51;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
public class EventConverter{
@Component
public static class StringToEventConverter implements Converter<String, Event> {
@Override
public Event convert(String source) {
return new Event(Integer.parseInt(source));
}
}
@Component
public static class EventToStringConverter implements Converter<Event, String> {
@Override
public String convert(Event source) {
return source.getId().toString();
}
}
}
package me.gracenam.demospring51;
import org.springframework.format.Formatter;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.Locale;
@Component
public class EventFormatter implements Formatter<Event> {
@Override
public Event parse(String text, Locale locale) throws ParseException {
return new Event(Integer.parseInt(text));
}
@Override
public String print(Event object, Locale locale) {
return object.getId().toString();
}
}
이렇게 Converter와 Formatter를 빈으로 등록한 후 실행해서 확인해보면 잘 동작하는 것을 볼 수 있다.
Reference
Comments
SPRING 의 다른 글
-
스프링 타입 컨터버 24 Jun 2022
-
API 예외 처리 17 Jun 2022
-
예외 처리와 오류 페이지 12 Jun 2022
-
로그인 처리 - 인터셉터 08 Jun 2022
-
로그인 처리 - 필터 06 Jun 2022
-
로그인 처리 - 쿠키, 세션 31 May 2022
-
Bean Validation 22 May 2022
-
검증 22 May 2022
-
메시지, 국제화 21 May 2022
-
타임리프 - 스프링 통합과 폼 19 May 2022
-
타임리프 - 기본 기능 10 May 2022
-
스프링 MVC 기본 기능 - 웹 페이지 만들기 02 May 2022
-
스프링 MVC 기본 기능 - HTTP 응답 30 Apr 2022
-
스프링 MVC 기본 기능 - HTTP 요청 24 Apr 2022
-
스프링 MVC 기본 기능 - 요청 매핑 19 Apr 2022
-
스프링 MVC 기본 기능 19 Apr 2022
-
스프링 MVC 구조 이해 14 Apr 2022
-
MVC 프레임워크 만들기 - V4, V5 12 Apr 2022
-
MVC 프레임워크 만들기 - V1, V2, V3 09 Apr 2022
-
서블릿, JSP, MVC 패턴 05 Apr 2022
-
서블릿 29 Mar 2022
-
웹 애플리케이션 이해 24 Mar 2022
-
스프링 웹 계층이란? 05 Nov 2021
-
스프링 시큐리티 공식문서 번역 27 Sep 2021
-
스프링 AOP 총정리 : 개념, 프록시 기반 AOP, @AOP 27 Apr 2021
-
SpEL (스프링 Expression Language) 25 Apr 2021
-
데이터 바인딩 추상화 : Converter와 Formatter 21 Apr 2021
-
데이터 바인딩 추상화 : PropertyEditor 12 Apr 2021
-
Validation 추상화 10 Apr 2021
-
Resource 추상화 08 Apr 2021
-
IoC 컨테이너 9부 07 Apr 2021
-
IoC 컨테이너 8부 06 Apr 2021
-
IoC 컨테이너 7부 02 Apr 2021
-
IoC 컨테이너 6부 29 Mar 2021
-
IoC 컨테이너 5부 27 Mar 2021
-
IoC 컨테이너 4부 23 Mar 2021
-
IoC 컨테이너 3부 20 Mar 2021
-
IoC 컨테이너 2부 18 Mar 2021
-
IoC 컨테이너 1부 12 Mar 2021
-
스프링 PSA 07 Jan 2021
-
스프링 @AOP 실습 07 Jan 2021
-
프록시 패턴 06 Jan 2021
-
스프링 AOP 04 Jan 2021
-
의존성 주입(Dependency Injection) 04 Jan 2021
-
스프링 빈(Bean) 02 Jan 2021
-
스프링 IoC 컨테이너 01 Jan 2021
-
스프링 IoC 01 Jan 2021