IT/Spring

Http Request의 Object 바인딩시 enum 으로 받는 방법

yeTi 2022. 5. 12. 17:57

안녕하세요. yeTi입니다.
오늘은 Spring Framework으로 http request를 처리할때 request stringenum으로 바인딩하는 방법을 보면서 Spring에서 제공하는 바인딩 서비스들을 확인해보겠습니다.

이전에 작성한 스프링 추전 강좌, 스프링 프레임워크 핵심 기술 내용 정리 및 후기 에도 관련 내용이 있습니다.

Converter 활용

Spring 에서는 모델을 바인딩하기 위해 Converter interface를 제공합니다.
그리고 http request가 들어오면 각 컨트롤러들은 BindingInitializer에 등록된 converter 들을 가지고 string 을 모델로 변환해줍니다.

이를 활용하여 http get 요청시 Path variableRequest param 에 정의한 EnumType 클래스로 변환할 수 있습니다.

스프링에서는 기본적으로 StringToEnum 클래스를 컨버터로 제공해주는데, 해당 클래스는 대문자에 대해서만 Enum으로 변경해주도록 되어있습니다.
따라서 다른 형식을 사용하고 싶다면 컨버터에 원하는 로직을 넣어 사용하면 됩니다.

Converter 빈 등록

@GetMapping("/enum/{type}")  
public ResponseEntity<Response> getData(@PathVariable(name = "enums") final EnumType enumType) {  
    ...
}

@GetMapping("/enum")  
public ResponseEntity<Response> getData(@RequestParam(name = "enums") final EnumType enumType) {  
    ...
}

---

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

@Component  
public class EnumTypeConverter implements Converter<String, EnumType> {  
    @Override  
    public EnumType convert(String source) {  
        return source.isEmpty() ? null:EnumType.valueOf(source.trim().toUpperCase());  
    }  
}

WebMvcConfigurer 에 converter 클래스 추가

@GetMapping("/enum/{type}")  
public ResponseEntity<Response> getData(@PathVariable(name = "enums") final EnumType enumType) {  
    ...
}

@GetMapping("/enum")  
public ResponseEntity<Response> getData(@RequestParam(name = "enums") final EnumType enumType) {  
    ...
}
---

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

public class EnumTypeConverter implements Converter<String, EnumType> {  
    @Override  
    public EnumType convert(String source) {  
        return source.isEmpty() ? null:EnumType.valueOf(source.trim().toUpperCase());  
    }  
}

---

@Configuration  
public class WebConfiguration implements WebMvcConfigurer {  

    @Override  
    public void addFormatters(FormatterRegistry registry) {  
        WebMvcConfigurer.super.addFormatters(registry);  
        registry.addConverter(new EnumTypeConverter());  
    }  
}

Formatter 활용

Converter 와 마찬가지로 모델을 바인딩하기 위한 interface입니다.
다만 converter보다 특화되어 사용하고 싶거나(Annotation 활용) 양방향 바인딩 활용, Locale 을 활용하고 싶을 때 사용할 수 있습니다.

대표적인 annotation formatter로는 @NubmerFormat@DateTimeFormat 이 있습니다.

Formatter 빈 등록

@GetMapping("/enum/{type}")  
public ResponseEntity<Response> getData(@PathVariable(name = "enums") final EnumType enumType) {  
    ...
}

@GetMapping("/enum")  
public ResponseEntity<Response> getData(@RequestParam(name = "enums") final EnumType enumType) {  
    ...
}

---

import org.springframework.format.Formatter;
...

@Component  
public class EnumTypeFormatter implements Formatter<EnumType> {  

    @Override  
  public EnumType parse(String text, Locale locale) throws ParseException {  
        return text.isEmpty() ? null: EnumType.valueOf(text.trim().toUpperCase());  
  }  

    @Override  
  public String print(EnumType enumType, Locale locale) {  
        return enumType.toString().toLowerCase();  
  }  
}

WebMvcConfigurer 에 formatter 클래스 추가

@GetMapping("/enum/{type}")  
public ResponseEntity<Response> getData(@PathVariable(name = "enums") final EnumType enumType) {  
    ...
}

@GetMapping("/enum")  
public ResponseEntity<Response> getData(@RequestParam(name = "enums") final EnumType enumType) {  
    ...
}
---

import org.springframework.format.Formatter;
...

public class EnumTypeFormatter implements Formatter<EnumType> {  

    @Override  
  public EnumType parse(String text, Locale locale) throws ParseException {  
        return text.isEmpty() ? null: EnumType.valueOf(text.trim().toUpperCase());
  }  

    @Override  
  public String print(EnumType enumType, Locale locale) {  
        return enumType.toString().toLowerCase();  
  }  
}

---

@Configuration  
public class WebConfigutation implements WebMvcConfigurer {  

    @Override  
  public void addFormatters(FormatterRegistry registry) {  
        WebMvcConfigurer.super.addFormatters(registry);  
        registry.addFormatter(new EnumTypeFormatter());  
  }  
}

Message Converter 활용

위에서 언급한 ConverterFormatter는 http request에 대하여 URL이나 query string 과 같은 http request parameter를 model object에 바인딩하는 역할을 합니다.

따라서 POST 와 같이 body로 전달된 XML이나 JSON 같은 데이터 포맷을 object로 변환하기 위해서는 message converter를 사용해야 합니다.

앞서 언급한것처럼 미디어 타입이 application/json 으로 들어오면 스프링 프레임워크는 등록된 messageConverter를 사용하는데 기본적으로 MappingJackson2HttpMessageConverter 를 사용하고 있습니다.

이 때, DeserializationContext 는 각 타입에 맞는 Deserializer 를 찾아서 object 변환을 수행하는데요.
Enum의 경우에는 기본적으로 BasicDeserializerFactory에서 EnumDeserializer 를 받아 사용하게 됩니다.

Deserializer 활용

Enum 을 사용하면서 커스텀한 로직을 사용하고 싶다면 @JsonCreator 사용하면 되는데요.
내부 함수에 @JsonCreator 어노테이션을 달면 FactoryBasedEnumDeserializer 가 반환되고 메소드가 등록되어 deserialize 할 때 동작합니다.

public enum EnumType{  

    @JsonCreator  
    public static EnumTypefrom(String s) {  
        return EnumType.valueOf(s.toUpperCase());  
    }
}

Setter 활용

Enum 을 사용하면서 커스텀한 로직을 사용하고 싶을때 deserialize 를 사용하지 않고 setter 를 사용하는 방법이 있는데요.

BeanDeserializerobject를 바인딩할때 object의 디폴트 생성자로 instance를 생성한 후에 setter를 사용해서 값을 맵핑 시킵니다.
이 때 정의된 setter가 있으면 해당 함수를 사용하여 값을 맵핑하기 때문에 setter에 커스텀한 로직을 넣어 사용할 수 있습니다.

public void setEnums(String enumsString) {  
    this.enums = EnumType.valueOf(enumsString.toUpperCase());  
}

참고 자료