스프링 부트에서 공통 에러를 처리할는 것은 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