본문 바로가기

Spring Boot

[webflux] Global Exception

스프링 부트에서 공통 에러를 처리할는 것은 MVC 와 조금 다르다. 

사용자 에러처리를 하든지 또는 스프링 부트 자체의 에러를 공통으로 처리하는 방법은 아래와 같다. 

 

GlobalException

import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

public class GlobalException extends ResponseStatusException {

    private static final long serialVersionUID = -1L;

    public GlobalException(HttpStatus status) {
        super(status);
    }

    public GlobalException(HttpStatus status, String reason) {
        super(status, reason);
    }

    public GlobalException(HttpStatus status, String reason, Throwable cause) {
        super(status, reason, cause);
    }
}

ResponseStatusException을 상속하여 공통 처리를 받을 수 있는 에러처리 클래스를 만들어 준다.

 

UnauthorizedException

import org.springframework.http.HttpStatus;

public class UnauthorizedException extends GlobalException {

    private static final long serialVersionUID = -1L;

    public UnauthorizedException(HttpStatus status) {
        super(status);
    }

    public UnauthorizedException(HttpStatus status, String reason) {
        super(status, reason);
    }

    public UnauthorizedException(HttpStatus status, String reason, Throwable cause) {
        super(status, reason, cause);
    }
}

일반적으로 사용하는 에러클래스는 GlobalException을 상속받아 만들어주고 필요시 아래처럼 throw 처리한다.

try {
    // something
} catch (Exception e) {
    log.error("JWT ERROR", e);
    throw new UnauthorizedException(HttpStatus.INTERNAL_SERVER_ERROR, Constants.ERROR_NO_AUTH, e);
}

 

GlobalErrorAttributes

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;

import java.util.Map;

@Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(request, options);

        Throwable throwable = getError(request);
        if (throwable instanceof GlobalException) {
            GlobalException ex = (GlobalException) getError(request);
            map.put("exception", ex.getClass().getSimpleName());
            map.put("message", ex.getMessage());
            map.put("status", ex.getStatus().value());
            map.put("error", ex.getStatus().getReasonPhrase());
            return map;
        }

        map.put("exception", "SystemException");
        map.put("message", throwable.getMessage());
        return map;
    }
}

DefaultErrorAttributes 는 스프링이 자동으로 만들어내는 에러를 담고 있으며, 해당 클래스를 통해 확장처리를 할 수 있다.

 

@RequestParam 등으로 처리되는 필수체크 등은 스프링 에러에 내용이 담기지 않는다.

해당 클래스를 통해 throwable 의 내용을 담아 사용자에게 메시지를 제대로 전달한다.

 

그리고 사용자 정의 에러일 경우는 GlobalException 을 통해 따로 처리된다.

 

GlobalErrorWebExceptionHandler

import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;

import java.util.Map;

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

    public GlobalErrorWebExceptionHandler(GlobalErrorAttributes g, ApplicationContext applicationContext,
                                          ServerCodecConfigurer serverCodecConfigurer) {
        super(g, new ResourceProperties(), applicationContext);
        super.setMessageWriters(serverCodecConfigurer.getWriters());
        super.setMessageReaders(serverCodecConfigurer.getReaders());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {

        final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults());

        return ServerResponse.status(HttpStatus.BAD_REQUEST)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(errorPropertiesMap));
    }
}

스프링에서 처리되는 에러처리를 중간에 가로채서 따로 처리한 내용으로 리턴한다. 스프링의 기본에러 처리 Order 는 -1 이다.

 

간단히 3개의 공통 파일과 사용자별 에러클래스를 생성하여 공통적으로 에러처리를 할 수 있다.

 

참고

https://programmer.help/blogs/webflux-rest-api-global-exception-handling-error-handling.html

 

Spring Boot 2.x series of tutorials: WebFlux REST API global exception handling Error Handling

Spring Boot 2.x series of tutorials: WebFlux REST API global exception handling Error Handling ABSTRACT: Origin: https://www.bysocket.com Content of this article Why global exception handling? WebFlux REST Global Exception Handling Practice Summary Abstrac

programmer.help