본문 바로가기

Spring/Springboot

📝 스프링(Spring) 예외(Exception)처리 2

Spring 기본적으로 BasicErrorController 에서 에러 처리

/error: 에러 요청 다시 전달(Redirect)하도록 WAS 설정되어 있음

 

  1. 오류(Error)페이지
  2. HTTP Status 에러
  3. HTTP 200, Error Message

 

-> 별도 설정 없으면 BasicErrorController가 에러를 처리함(기본적으로 WebMVCAutoConfiguration을 통해 WAS 자동 설정)

 

스프링 일반적 흐름

WAS(톰캣) -> Filter -> 디스패치 서블릿 -> 인터셉터 -> 컨트롤러

 

컨트롤러에서 에러 발생 시 흐름(예외처리 따로 없는 경우)

WAS(톰캣) -> Filter -> 디스패치 서블릿 -> 인터셉터 -> 컨트롤러

-> WAS는 application에서 예외처리 안되서 온거로 보고 BasicErrorController 호출

 

위와 같이 기본적인 에러 발생 흐름은 컨트롤러를 호출하는 것, 필터나 인터셉터가 재 호출 될 수 있으므로 별도의 설정이 필요함

 

서블릿 디스패처: 디스패처 Type으로 요청의 종류를 구분함, 일반 요청 Type은 Request, 에러 요청 Type는 Error

 

 

필터: 디스패처 서블릿 기술, 필터 등록 할 때 호출될 디스패처Type 설정 가능, 별도 설정 없을 시 Request Type의 경우에만 필터 호출 됨

 

인터셉터: 스프링 기술로 디스패처Type을 설정 할 수 없음, URI 패턴으로 처리해야함

But 스프링 부트에서는 WAS까지 직접 제어하게 되었음, 기존에 컨트롤러 요청이 2번되었는데 1번의 요청이 2번 전달되는 것임

클라이언트도 에러처리가 어떻게 된건지 알 수 없음 

 

BasicErrorController

accept 헤더에 따라 에러 페이지(errorHtml)를 반환하거나 에러 메시지(error)를 반환

기본 경로: /error <- Properties에서 server.error.path로 변경 가능

에러 페이지(errorHtml), 에러 메시지(error) 둘 다 getErrorAttributeoptions를 호출 -> 반환할 에러 속성을 얻음

기본적으로는 DefaultErrorAttributes로 부터 반환할 정보를 가져옴

DefaultErrorAttributes 전체 항목 중 불필요한 속성을 제거하여 선택해서 사용하면 됨(설정은 Properties에서 설정)

항목

timestamp: 에러가 발생한 시간

status: 에러의 HTTP상태

error: 에러코드

path: 에러 발생한 URI

exception: 최상위 예외 클래스의 이름(설정 필요)

message: 에러에 대한 내용(설정 필요)

errors: BindingException에 의해 생긴 에러 목록(설정 필요)

 

하지만 이렇게 설정을 해도 에러 상태는 500으로 확인됨

왜? 에러 처리가 된 것이 아닌 WAS가 에러 전달을 받은 것임

 

 

Spring은 에러 처리라는 공통 관심사(Cross-Cuting concerns) 를 메인 로직으로 부터 분리

예외처리 전략을 추상화한 HandlerExceptionResolver 인터페이스를 만들었음

 

 HandlerExceptionResolver: 발생한 Exception을 Catch 하여 HTTP 상태 응답 메시지를 설정

예외를 설정하므로 WAS에서는 해당 요청을 정상 응답으로 인식(WAS로 에러 전달이 진행되지 않음)

 

예외처리를 위해  HandlerExceptionResolver의 구현체들을 빈(Bean)으로 등록해서 관리

적용 가능한 구현체를 찾아 예외 처리함

(우선순위 순서)

1. DefaultErrorAttributes: 에러 속성을 저장 및 관리, 직접 예외 처리 하지 않음

2. ExceptionHandlerExceptionResolver: 에러 응답을 위한 @ControllerAdvice, @RestControllerAdvice에 있는 @ExceptionHandler처리

3. ResponseStatusExceptionResolver: HTTP 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException 처리

4. DefaultHandlerExceptionResolver: 스프링 내부 기본 예외 처리

 

직접 예외 처리를 하는 ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver는 컴포지트 패턴을 적용하여 HandlerExceptionResolverComposite으로 모아서 관리함

 

Spring은 아래 도구를 이용하여 ExceptionResolver를 동작시켜 에러를 처리함

 

1. @ResponseStatus: 에러 HTTP 상태 변경

적용대상

Exception 클래스 자체, 메소드에 @ExceptionHandler와 함께, 클래스에 @RestControllerAdvice와 함께

 

적용 방법

@ResponseStatus(value = Http.Status.Not_Found)의 형식으로 지정

 

적용된 응답

ResponseStatusExceptionResolver가 지정한 상태로 에러를 응답한다.

응답은 BasicErrorController에 의해 응답함, ResponseStatusExceptionResolver는 WAS까지 예외를 전달한다는 것임

 

한계점

에러 응답 내용(Payload)를 수정하기 번거롭다(DefaultErrorAttribute를 수정해야함)

예외 클래스와 강하게 결함되어 같은 예외는 같은 메시지를 응답함

별도의 응답 상태가 필요하면 예외 클래스를 추가해야 함

WAS까지 에러가 전달되므로 WAS의 에러 요청 흐름으로 진행

외부에서 정의한 Exception 클래스에는 @ResponseStatus를 붙일 수 없음 -> 대안으로 Spring 5부터 ResponseStatusException이 추가 되었음

 

단 Properties설정으로 에러응답 일부를 커스터마이징 가능, 메시지를 이용하여 처리할 수 있음 하지만 개발자가 원하는대로 예외처리하는데 어려움이 있음

 

 

 

2. ResponseStatusException

HttpStatus와 함께 선택적으로 Response와 cause를 추가할 수 있음

UnCheckedException을 상속받아 명시적으로 에러를 처리해주지 않아도 됨(@ResponseStatus와 다른점??)

ResponseStatusExceptionResolver가 에러를 처리해줌

 

장점

기본적인 예외처리 빠르게 적용, 손쉬운 프로토타이핑

HttpStatus를 직접 설정하여 에러 클래스와 결합도를 낮춤

불필요하게 많은 별도의 예외 클래스를 만들지 않아도 됨

프로그래밍 방식으로 예외를 직접 생성하므로 예외를 더욱 잘 제어할 수 있음

 

한계

직접 예외처리 프로그래밍을 하므로 일관된 예외처리가 어려울 수 있음

예외처리 코드가 중복될 가능성이 있음

Spring 내부의 예외 처리는 어려움

예외가 WAS까지 전달되므로 WAS의 에러 요청 흐름으로 진행

 

 

3. @ExceptionHandler

유연한 에러 처리 가능

 

사용범위

컨트롤러의 메소드, @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드

 

예외처리 ExceptionHandlerExeptionResolver

 

Exception 클래스들을 속성으로 받아서 처리할 예외를 지정할 수 있음

만약 @ExceptionHandler에 예외 클래스를 지정하지 않으면 파라미터에 설정된 에러 클래스를 처리함

 

@ResponseStatus와 결합이 가능함, 만약 ResponseEntity에서도 Status를 지정하고 @ResponseStatus도 있으면 ResponseEntity가 우선 순위를 갖음

 

ExceptionHandler는 @ResponseStatus와 달리 에러 응답(Payload)를 자유롭게 다룰 수 있음

 

code: 에러코드 종류로 내부 정의 값 보다 BAD_REQUEST와 같은 Http 표준 상태와 같게 사용하는 것이 가독성이 좋고 유지보수가 쉬움

message: 에러 발생 이유 설명

errors: 어떤값이 잘못되어 @Valid에 의한 검증이 실패한 것인지를 위한 에러 목록

 

 

Spring은 예외 발생 시 가장 적합한 예외 핸들러를 찾고 없으면 부모 예외 핸들러를 찾음

ex. NullPointException 발생했고 예외 처리기가 없을 시 Exception에 대한 처리기를 찾음

사용 주의사항: @ExceptionHandler에 등록된 예외 클래스와 파라미터로 받는 예외 클래스가 동일 해야함, 값이 다르면 런타임 시점에 에러 발생

ExceptionHandler의 파라미터로 HttpServeletRequest 나 WebRequest 등 을 얻을 수 있으며 반환 타입은 ResponsEntity, String, void등 자유롭게 활용이 가능함

 

특정 컨트롤러 에러 처리

1. 해당 컨트롤러에 @ExceptionHandler 작성

2. 예외 처리 클래스에 @ControllerAdvice(특정컨트롤러.class), @RestControllerAdvice(특정컨트롤러.class) 을 붙이고 처리할  @ExceptionHandler 작성

// PostController.class에만 적용하는 경우
@RestControllerAdvice(basePackageClasses = PostController.class) 
public class GlobalExceptionHandler {
// 생략
}


// 패키지 단위로 적용하는 경우
@RestControllerAdvice(basePackages = "com.app.project.controller") 
public class GlobalExceptionHandler {
// 생략
}

 

 

대표적 예외인 잘못된 URI 호출은 NoHandlerFoundException이 있음

Spring은 스프링 예외 처리를 ResponseEntityHandler를 추상클래스로 제공함

그리고 ResponseEntityExceptionHandler는 스프링 예외에 대한 ExceptionHandler가 모두 구현되어 있음

그러므로 ControllerAdvice 클래스가 ResponseEntityExceptionHandler를 상속 받게하는 것이 좋음

만약 ResponseEntityExceptionHandler를 상속하지 않으면 스프링 예외는 DefaultHandlerException이 처리함 

-> 예외 응답이 달라지므로 일관된 응답을 할 수 없음, 그래서 ResponseEntityExceptionHandler를 상속시키는 것이 중요함

그리고 ResponseEntityExceptionHandler는 에러 메시지를 반환 안하는 것이 기본값(Default)이므로 메소드 오버라이딩 필요

public abstract class ResponseEntityExceptionHandler {
    ...

    protected ResponseEntity<Object> handleExceptionInternal(
        Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
            
        ...
    }
}

 

장점

하나의 클래스로 전역적 예외 처리 가능

직접 정의한 에러 응답을 일관성 있게 응답할 수 있음

try~catch 형태가 아니므로 코드 가독성이 좋음

 

주의사항

여러 @ControllerAdvice가 존재 시 @Order로 순서 지정 필수, 스프링이 @ControllerAdvice 순서를 임의로 처리할 수 있음

그래서 한 프로젝트 당 하나의 @ControllerAdvice로 관리하는 것이 좋음

만약 @ControllerAdvice가 여러개 필요하다면 basePackages나 @Order 지정 필요

직접 구현한 Exception 클래스들을 한 공 간에서 관리

 

 

 

레퍼런스🙇🏻‍♀️

https://mangkyu.tistory.com/204

 

[Spring] 스프링의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)

예외 처리는 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 어떠한 방법들이 있고 가장 좋은 방법(Best Practice)은 무엇인

mangkyu.tistory.com