Spring/Spring Core

[Spring] 데이터 바인딩 추상화 - Converter, Formatter

TheWing 2020. 11. 5. 03:49

Converter, Formatter

1. Converter 인터페이스

Converter 인터페이스 API

Converter 특징

  • Spring 3.0 부터 생성되었다
  • S 타입을 T 타입으로 변환할 수 있는 매우 일반적인 변환기이다
  • Spring이 제공하는 ConversionService 인터페이스를 통해 사용된다.
  • 상태정보(값)을 저장하지 않으므로 Thread-safe하다
    • 멀티 쓰레드에 안전하고 값이 일치하고 원하는 값이 나온다.
  • Bean으로 등록해서 사용이 가능하다
  • PropertyEditor의 단점을 보완하기 위해 생겼다.

1) Converter 구현 예제

import org.springframework.core.convert.converter.Converter;

public class EventConverter {
    //상태 정보가 없기 때문에 Component 빈으로 등록해서 사용이 가능하다
    //Converter 인터페이스를 구현해서 사용할 일은 없다
    public static class StringToEventConverter implements Converter<String, Event> {
        @Override
        public Event convert(String source) {
            //source를 받아서 Event 객체를 생성해준다
            return new Event(Integer.parseInt(source));
        }
    }

    public static class EventToStringConverter implements Converter<Event , String> {
        @Override
        public String convert(Event source) {
            //source를 받아서 Id를 리턴해준다
            return source.getId().toString();
        }
    }
}
  • EventConverter 클래스에 static InnerClassStringToEventConverterEventToStringConverterConverter 인터페이스를 구현하여 생성한다

Converter 인터페이스 내부를 보면

 * @param <S> the source type
 * @param <T> the target type
 */
@FunctionalInterface
public interface Converter<S, T> {

    @Nullable
    T convert(S source);

}

Converter Interface

이렇게 되어있다

  • Converter 인터페이스는 두 개의 Generic Type을 받는다. 첫 번째 Type S는 source Type, 두 번째 Type T는 target Type 이다.
  • String을 Event로 변환해야 하므로 Converter<String, Event> 만 구현한다.
  • 결과적으로 StringToEventConverterEventToStringConverter 클래스가 PropertyEditor 예제인 EventEditor와 동일하다

2) Converter 사용 예제

Spring Boot가 아닌 Spring Web MVC 사용할 경우 - WebConfig에 등록한다

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

Spring Boot 사용할 경우 - Converter를 빈으로 등록한다

  • @Component 애노테이션으로 사용하여 빈으로 만들어서 등록해주면된다
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 s) {
            return new Event(Integer.parseInt(s));
        }
    }

    @Component
    public static class EventToStringConverter implements Converter<Event, String> {
        @Override
        public String convert(Event event) {
            return event.getId().toString();
        }
    }
}

테스트 결과

2. Formatter 인터페이스

Formatter 인터페이스 API

특징

  • Spring 3.0 부터 생성되었다.
  • PropertyEditor의 대체제이다
  • Object 와 String 간의 변환을 담당한다
  • Spring이 제공하는 ConversionService 인터페이스를 통해 사용된다.
  • 상태정보(값)을 저장하지 않으므로 Thread-safe하다
    • 멀티 쓰레드에 안전하고 값이 일치하고 원하는 값이 나온다.
  • Bean으로 등록해서 사용이 가능하다
  • Locale 정보를 기반으로 다국화하여 바꿀 수 있다.

1) Formatter 구현 예

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 인터페이스 구현해서EventFormmatter 클래스를 생성한다.
  • Generic Type을 Formatter로 처리하는 타입을 하나 준다
  • 두 개의 메소드만 구현하면된다 parse(), print()
  • 문자를 받아서 객체로 다른 하나는 객체를 받아서 문자열로 , locale 정보를 기반으로 바꿀 수 있다.

2) Formatter 사용 예제

Spring Boot가 아닌 Spring Web MVC 사용할 경우 - WebConfig에 등록한다

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

Spring Boot 사용할 경우 - Converter를 빈으로 등록한다

  • @Component 애노테이션으로 사용하여 빈으로 만들어서 등록해주면된다
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();
    }
}

테스트 결과

3. ConversionService

ConversionService 인터페이스 API

특징

  • PropertyEditor를 DataBinder 를 통해 사용했다면 ConversionService를 통해 등록되어사용한다
  • FormatterRegistry의 addFormatters() 메소드로 등록한 Converter 와 Formatter는 ConversionService에 등록되고 ConversionService를 통해 Thread-safe하게 변하는 작업을 수행한다
  • Spring MVC, Bean(Value) 설정 , SpEL 에서 사용된다
  • Spring이 제공해주는 여러가지 ConversionService 구현체 중에 DefaultFormattingConversionService이 자주 사용된다.
    • FormatterRegistry , ConversionService 를 구현하여 기능을 한다
    • 여러 기본 Converter 와 Formatter 등록 해준다.
  • Converter를 레지스트리에 등록할때는 ConverterRegistry에 등록 해야하고, Formatter는 FormatterRegistry에 등록해야한다
  • FormatterRegistry는 사실상 ConverterRegistry를 상속받는다
  • FormatterRegistry는 Converter도 등록이 가능하다
  • ConversionService도 가지고 있다.

구조

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() = "+conversionService.getClass().toString());
    }
}

테스트 결과

conversionService.getClass() = class org.springframework.boot.autoconfigure.web.format.WebConversionService

  • 위와 같이 ConversionService 를 빈으로 등록해서 사용하는 경우도 없다

Spring Boot 관련 내용

  • 웹 애플리케이션인 경우에 DefaultFormattingConversionSerivce를 상속하여 만든
    WebConversionService를 빈으로 등록해 준다.
  • Formatter와 Converter 빈이 자동으로 등록되어 있으면 빈들은 ConversionService에 자동으로 등록해준다
  • StringToEventConverterEventToStringConverter@Component로 빈 등록을 해준다

Converter를 빈으로 등록시 예제


public class EventConverter {
        @Component
    public static class StringToEventConverter implements Converter<String, Event> {
        @Override
        public Event convert(String source) {
            //source를 받아서 Event 객체를 생성해준다
            return new Event(Integer.parseInt(source));
        }
    }
        @Component
    public static class EventToStringConverter implements Converter<Event , String> {
        @Override
        public String convert(Event source) {
            //source를 받아서 Id를 리턴해준다
            return source.getId().toString();
        }
    }
}

테스트 결과

Formatter를 빈으로 등록시 예제

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

테스트 결과

테스트

  • @WebMvcTest
    • WebMvcTest slicing test이다
    • 계층형 테스트 Web과 관련된 bean만 등록해준다
    • 주로 Controller만 등록해준다 WebMvcTest에 Convertor나 Formatter를 등록한다
@RunWith(SpringRunner.class)
@WebMvcTest({EventFormatter.class,EventController.class}) //1번
// @WebMvcTest({EventConverter.StringToEventConverter.class,EventController.class}) // 2번
public class EventControllerTest {
    @Autowired
    MockMvc mockMvc;

    @Test
    public void getTest() throws Exception {
                //get 요청 1을 보낸다
                //status가 200으로 나오길 바란다
                //content가 1이 나오길 바란다.
        mockMvc.perform(get("/event/1"))
                .andExpect(status().isOk())
                .andExpect(content().string("1"));

    }
}

테스트 결과

  • 1번 @WebMvcTest({EventFormatter.class,EventController.class}) 테스트
  • 2번 @WebMvcTest({EventConverter.StringToEventConverter.class,EventController.class}) 주석 풀고 1번 주석한후 테스트하면 둘다 결과가 같다
  • 1번 결과

  • 2번 결과
  • 일반적인 Class만 컴포넌트로 주입한다 해도 되지 않는다
  • @WebMvcTest 클래스 테스트 조건은 ComponentScan 이 가능한 클래스(Component, RestController) 만 가능하다

 

ConversionService에 등록되어있는 Converter들을 확인하는 방법

  • ConversionService 인스턴스를 출력해보면 된다.
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);
        }
}
  • 결과

  • 기본적으로 빈으로 등록되어 있는 Converter 들이 출력된다
  • 필요한건 toString() 메소드로 출력해보면된다

결론

  • 보통 데이터 바인딩을 Web과 관련하여 개발하기 때문에 Formatter 방법을 사용하는 것을 추천한다

References