타입 변환 - SpringBoot

Jiwon Park·2023년 5월 24일
0

스프링은 내부에서 수 많은 기본 컨버터들을 제공한다.
컨버터를 추가하면 추가한 컨버터가 기본 컨버터 보다 높은 우선순위를 가진다

스프링에서는 이러한 기본 컨버터들이 자동 변환을 해주지만 자동 변환이 안되는 경우 직접 만들어서 등록하면 된다.

컨버터 구현 예시

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

컨버터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToIpPortConverter());
        //registry.addConverter(new IpPortToStringConverter());
        //registry.addConverter(new StringToIntergerConverter());
    }
}

스프링은 내부에서 ConversionService 를 제공한다.
WebMvcConfigurer 가 제공하는 addFormatters() 를 사용해서 추가하고 싶은 컨버터를 등록하면 스프링은 내부에서 ConversionService 에 컨버터를 추가해준다.


  	@Test
    void stringToInteger(){
        IpPort result = new StringToIpPortConverter().converter.convert("127.0.0.1:8080";
        assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080));
    }
    
    @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";
	}
    

실행
http://localhost:8080/ip-port?ipPort=127.0.0.1:8080
실행 로그
StringToIpPortConverter : convert source=127.0.0.1:8080
ipPort IP = 127.0.0.1
ipPort PORT = 8080


컨버터는 어느 타입이든 변경 할 수 있는 범용적인 타입 변환 기능을 제공한다.
실제 개발 과정에서는 문자를 다른 타입으로 변환하거나, 다른 타입을 문자로 변환하는 경우가 많을텐데 이 경우에 특화된 기능인 포맷터가 있다.
우선순위는 컨버터가 우선한다.

포맷터 구현 예시

@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);
         return format.parse(text);
     }
     @Override
     public String print(Number object, Locale locale) {
         log.info("object={}, locale={}", object, locale);
         return NumberFormat.getInstance(locale).format(object);
     }
}

컨버터 + 포맷터 테스트

public class FormattingConversionServiceTest {
   @Test
   void formattingConversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
  	 //컨버터 등록
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());
  	 //포맷터 등록
        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(1000L); //Long 타입 주의
   }
}

FormattingConversionService 는 ConversionService 관련 기능을 상속받기 때문에 결과적으로
컨버터도 포맷터도 모두 등록할 수 있다. 그리고 사용할 때는 ConversionService 가 제공하는 convert를 사용하면 된다.

등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
 @Override
 public void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new StringToIpPortConverter());
    registry.addConverter(new IpPortToStringConverter());
 	//추가
 	registry.addFormatter(new MyNumberFormatter());
 }
}

포맷터를 지원하는 컨버전 서비스를 사용해 포맷터를 추가할 수 있다. 내부에서 어댑터 패턴을 사용해서 포맷터가 컨버터처럼 동작하도록 지원한다.


포맷터 어노테이션

스프링에서는 애노테이션 기반의 두가지 포맷터도 제공하는데 이를 사용하면 편하게 객체의 각 필드마다 다른 원하는 형식으로 포맷을 지정할 수 있다.

@NumberFormat: 숫자 관련 형식 지정 포맷터 사용, NumberFormatAnnotationFormatterFactory
@DateTimeFormat: 날짜 관련 형식 지정 포맷터 사용, Jsr310DateTimeFormatAnnotationFormatterFactory

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

0개의 댓글