[스프링 MVC - 2편] 스프링 타입 컨버터

지현·2022년 1월 9일
0

스프링

목록 보기
31/32

스프링 타입 컨버터 소개

  • 개발자가 새로운 타입을 만들어서 변환하고 싶으면 (ex) String "true"로 입력받은 것을 boolean형으로 변환) 스프링이 제공하는 확장 가능한 컨버터 인터페이스를 사용
  • 개발자는 스프링에 추가적인 타입 변환이 필요하면 이 컨버터 인터페이스를 구현해서 등록
  • 컨버터 인터페이스는 모든 타입에 적용 가능

타입 컨버터 - Converter

  • 타입 컨버터를 사용하려면 org.springframework.core.convert.converter.Converter 인터페이스를 구현
  • 스프링은 문자, 숫자, 불린, Enum등 일반적인 타입에 대한 대부분의 컨버터를 기본으로 제공

IpPort객체 -> String

@Slf4j
public class IpPortToStringConverter implements Converter<IpPort,String> {
    @Override
    public String convert(IpPort source) {
        log.info("convert source={}",source);
        //IpPort 객체 -> "127.0.0.1:8080"
        return source.getIp()+":"+source.getPort();
    }
}

String -> IpPort 객체

@Slf4j
public class StringToIpPortConverter implements Converter<String, IpPort> {
    @Override
    public IpPort convert(String source) {
        log.info("convert source={}",source);
        //"127.0.0.1:8080" -> IpPort 객체
        String[] split = source.split(":");
        String ip=split[0];
        int port=Integer.parseInt(split[1]);
        return new IpPort(ip,port);
    }
}

테스트 코드

  @Test
    void stringToIpPort(){
        IpPortToStringConverter converter = new IpPortToStringConverter();
        IpPort source = new IpPort("127.0.0.1", 8080);
        String result = converter.convert(source);
        assertThat(result).isEqualTo("127.0.0.1:8080");
    }

    @Test
    void ipPortToString(){
        StringToIpPortConverter converter = new StringToIpPortConverter();
        String source="127.0.0.1:8080";
        IpPort result = converter.convert(source);
        assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080));
        //롬복의 @EqualsAndHashCode 를 넣으면 
        //모든 필드를 사용해서 equals() , hashcode() 를 생성 
        //따라서 모든 필드의 값이 같다면 a.equals(b) 의 결과가 참 (IpPort에 적용되어있음)

컨버전 서비스 - ConversionService

  • 타입 컨버터를 하나하나 직접 찾아서 타입 변환에 사용하는 것은 매우 불편
  • 스프링은 개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용할 수 있는 컨버전 서비스(ConversionService) 기능을 제공
@Test
    void conversionService(){
        //등록 (사용할 컨버터들을 컨버전 서비스에 등록)
        DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToIntegerConverter());
        conversionService.addConverter(new IntegerToStringConverter());
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());

        //사용 conversionService.convert(데이터, 변환하고싶은 데이터 타입)
        assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
        assertThat(conversionService.convert(10, String.class)).isEqualTo("10");
        IpPort result = conversionService.convert("127.0.0.1:8080", IpPort.class);
        assertThat(result).isEqualTo(new IpPort("127.0.0.1",8080));

        String ipPortString = 
             conversionService.convert(new IpPort("127.0.0.1", 8080), String.class);
        assertThat(ipPortString).isEqualTo("127.0.0.1:8080");


    }
  • 컨버터를 등록할 때는 타입 컨버터를 명확하게 알아야 하지만, 컨버터를 사용하는 입장에서는 타입 컨버터를 전혀 몰라도 됨
  • 컨버전 서비스가 적절한 컨버터를 찾아서 실행해줌
  • 실제로 구현할 때는 컨버전 서비스를 등록하는 부분과 사용하는 부분을 분리하고 의존관계 주입을 사용해야 함

스프링에 Converter 적용하기

컨버터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToIntegerConverter());
        registry.addConverter(new IntegerToStringConverter());
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());
    }
}
  • WebMvcConfigurer가 제공하는 addFormatters()를 사용해서 추가하고 싶은 컨버터를 등록
  • 스프링은 내부에서 제공하는 ConversionService에 컨버터를 추가
  • 컨버터를 추가하면 추가한 컨버터가 기본 컨버터 보다 높은 우선순위를 가짐

실행

    @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";
    }
  • @RequestParam@RequestParam을 처리하는 ArgumentResolver에서 ConversionService를 사용해서 타입을 변환
  • @RequestParam, @ModelAttribute, @PathVariable에서 다 적용됨

뷰 템플릿에 컨버터 적용하기

  • 타임리프는 렌더링 시에 컨버터를 적용해서 렌더링 하는 방법을 편리하게 지원
  • 타임리프는 텍스트로 출력(객체 -> 문자, 숫자 -> 문자 등으로 변환)
  • 변수 표현식 (컨버터 적용 X) : ${...}
  • 컨버전 서비스 적용 : ${{...}}
  • th:field는 컨버터 서비스를 자동으로 적용 (${{}} 사용하지 않아도 바로 적용이 됨)

포맷터 - Formatter

  • 포맷터는 컨버터의 특별한 버전
  • 객체를 특정한 포멧에 맞추어 문자로 출력하거나 또는 그 반대의 역할을 하는 것에 특화된 기능
  • Converter vs Formatter
    • Converter는 범용(객체를 아무 객체로 바꿀 수 있음)
    • Formatter는 문자에 특화(객체->문자, 문자->객체) + 현지화(Locale) 기능
@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);

        NumberFormat instance = NumberFormat.getInstance(locale);
        return instance.format(object);
    }
}
  • 자바가 기본으로 제공하는 NumberFormat객체를 사용하면 Locale 정보를 활용해서 나라별로 다른 숫자 포맷을 만들어줌
  • parse()의 결과는 Long

포맷터를 지원하는 컨버전 서비스

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


    }
  • FormattingConversionService는 포맷터를 지원하는 컨버전 서비스
  • ConversionService 관련 기능을 상속받기 때문에 결과적으로 컨버터도 포맷터도 모두 등록할 수 있음

포맷터 적용하기

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        //주석처리 우선순위
       // registry.addConverter(new StringToIntegerConverter());
       // registry.addConverter(new IntegerToStringConverter());
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());

        //포맷터 추가
        registry.addFormatter(new MyNumberFormatter());
    }
}
  • 컨버터가 포맷터보다 우선순위가 더 높음

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

스프링은 자바에서 기본으로 제공하는 타입들에 대해 수 많은 포맷터를 기본으로 제공

    @Data
    static class Form{
        @NumberFormat(pattern = "###,###") //내가 원하는 형식 지정
        private Integer number;

        @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
        private LocalDateTime localDateTime;
    }
  • 애너테이션이 있으면 스프링이 기본으로 제공하는 포맷터가 적용이 됨
  • @NumberFormat : 숫자 관련 형식 지정 포맷터 사용
  • @DateTimeFormat : 날짜 관련 형식 지정 포맷터 사용
  • 문자->객체, 객체->문자 양뱡향 전환이 가능

주의

  • 메시지 컨버터(HttpMessageConverter)에는 컨버전 서비스가 적용되지 않음
  • HttpMessageConverter의 역할은 단지 HTTP 메시지 바디의 내용을 객체로 변환하거나 객체를 HTTP 메시지 바디에 입력하는 것
  • 컨버전 서비스는 @RequestParam , @ModelAttribute , @PathVariable, 뷰 템플릿 등에서 사용할 수 있음
  • 메시지 컨버터는 컨버전 서비스와 전혀 관계가 없음


출처
[인프런] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

0개의 댓글