본문 바로가기
프로그래밍/webflux

[Spring] problem spring web을 이용해보자 - webFlux 적용편

by 사바라다 2022. 3. 6.

안녕하세요. 이전 시간의 포스팅에서 problem spring web을 Spring MVC에 적용하는 시간을 가져보았습니다. 오늘은 이를 webflux에 적용해보는 시간을 가져보도록 하겠습니다.

Spring WebFlux

의존성

problem spring web을 사용하기 위해서는 아래의 의존성이 필요합니다. Spring Web과 전체적인 이름은 유사하지만 동일하지는 않기 때문에 주의하시기 바랍니다.

implements("org.zalando:problem-spring-webflux:0.26.2")

환경설정(Configuration)

의존성을 넣어주었으면 이제 환경설정을 해볼 차례입니다. 환경설정은 problemModule에 대한 설정과 json으로 응답값을 만들어 낼때의 mapper를 변경하는 설정이 필요합니다.

먼저 problemModule을 설정해보도록 하겠습니다. 제가 설정한 ProblemModule의 설정은 아래와 같습니다.

@Bean
public final ProblemModule problemModule() {
    return new ProblemModule()
    .withStackTraces(false); // stacktrace를 노출하지 않습니다.
}

@Bean
public final ConstraintViolationProblemModule constraintViolationProblemModule() { // 제약조건 검증에러에도 모듈을 적용합니다.
    return new ConstraintViolationProblemModule();
}

@Bean
@Order(-2) // WebFluxResponseStatusExceptionHandler 보다 빠르게 처리할 수 있도록 순서를 수정합니다.
public final WebExceptionHandler problemExceptionHandler(ObjectMapper mapper,ProblemHandling problemHandling) {
    return new ProblemExceptionHandler(mapper, problemHandling);
}

3번째 bean 등록은 중요합니다. webFlux에서는 request handler가 호출되지 않으면 ControllerAdvice가 사용되지 않습니다. 그렇기 때문에 이때 커스터마이징 Exception을 사용하기 위해서는 WebFluxResponseStatusExceptionHandler 보다 ProblemExceptionHandler가 먼저 실행될 필요가 있습니다. 그래서 @Order 어노테이션을 통해 순서를 정해주는것이 중요합니다. 해당 Bean을 통해 아래의 Trait이 사용가능해집니다.

  • ResponseStatusAdviceTrait
  • NotAcceptableAdviceTrait
  • UnsupportedMediaTypeAdviceTrait

그리고 이어서 아래의 설정은 response 하는 jackson의 모듈을 커스터마이징하는 코드입니다. registerModule로 ObjectMapper를 커스터마이징하고 이를 WebFluxConfigurer Encoder, Decoder로 넣어두는것이 중요합니다.

@Configuration
public class WebConfig {

   @Bean
   @Primary
   public ObjectMapper objectMapper(ProblemModule problem) {
      ObjectMapper objectMapper = ObjectMapper();
      objectMapper.registerModule(problem);
      return objectMapper;
   }

   @Bean
   public Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper) {
      return new Jackson2JsonEncoder(mapper);
   }

   @Bean
   public Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper) {
      return new Jackson2JsonDecoder(mapper);
   }

   @Bean
   public WebFluxConfigurer webFluxConfigurer(final Jackson2JsonEncoder encoder, final Jackson2JsonDecoder decoder) {
      return new WebFluxConfigurer() {

        @Override
         public void configureHttpMessageCodec(ServerCodecConfigurer configurer) {
            configurer.defaultCodecs().jackson2JsonEncoder((Encoder)encoder);
            configurer.defaultCodecs().jackson2JsonDecoder((Decoder)decoder);
         }
      };
   }
}

기본 사용 방법

위의 설정이 완료되셨다면 아래의 코드만으로 problem spring web을 webflux에서 사용할 수 있습니다.

@ControllerAdvice
class ExceptionHandling implements ProblemHandling {

}

위 설정만으로 적용되는 에러 응답의 예는 아래와 같습니다.

커스터 마이징 에러 반환하기

일반적인 로직에 대해서 커스터 마이징 에러를 반환하기 위해서는 Trait을 새롭게 만들어주고 관련된 핸들러를 만들어주면 가능합니다. 사용방법은 아래처럼 먼저 AdviceTrait을 상속받는 interface를 만들어줍니다. 그리고 @ExceptionHandler를 적용해주면 됩니다.

public interface SabaradaProblemAdviceTrait extends AdviceTrait {

   default ThrowableProblem defaultProblem(Throwable ex, Status status) {
      ThrowableProblem problem = 
      prepare(ex, status, Problem.DEFAULT_TYPE)
      .with("errorCode", 1000)
      .build();

      return problem;
   }

   @ExceptionHandler
   default Mono<ResponseEntity<Problem>> handleException(Exception ex, final ServerWebExchange request) {
      return create(defaultProblem(ex, Status.INTERNAL_SERVER_ERROR), request);
   }
}

커스텀 interface를 만들었다면 이를 위에서 만든 ControllerAdvice 어노테이션이 붙은 ExceptionHandling에 implements에 추가하면 바로 적용되게됩니다.

@ControllerAdvice
class ExceptionHandling implements ProblemHandling, SabaradaProblemAdviceTrait {

}

위 설정을 하게되었을 때 아래와 같은 json 응답을 받을 수 있게 됩니다.

{
  "title": "Unsupported Media Type",
  "status": 415,
  "detail": "415 UNSUPPORTED_MEDIA_TYPE \"Content type '' not supported\""
}

마무리

오늘은 이렇게 이전 시간에 이어서 probleum spring web을 webFlux에 적용하는 방법을 알아보았습니다.

감사합니다.

참조

https://github.com/zalando/problem-spring-web/tree/main/problem-spring-webflux

댓글