스프링부트의 예외 처리 방식

마라탕천재 ㅣ 2024. 9. 9. 23:43

1. 예외 처리의 필요성

예외가 발생했을 때 클라이언트에게 오류 메시지를 전달하려면, 애플리케이션의 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 적절히 전달해야 한다. 스프링부트에서는 이 과정을 체계적으로 처리하기 위한 다양한 방법을 제공한다.

  • @(Rest)ControllerAdvice 및 @ExceptionHandler 사용
    • @RestControllerAdvice는 모든 컨트롤러에서 발생한 예외를 처리하는 역할을 한다.
    • @ControllerAdvice 대신 @RestControllerAdvice를 사용하면, 예외 처리 결과를 JSON 형태로 클라이언트에게 전달할 수 있다.
  • @ExceptionHandler 사용
    • @ExceptionHandler는 특정 컨트롤러에서 발생한 예외를 처리할 수 있도록 해준다. 이를 통해 컨트롤러별로 맞춤형 예외 처리가 가능하다.

 

 

2. 전역 범위 예외 처리

@RestControllerAdvice
public class CustomExceptionHandler {

    private final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionHandler.class);

    // RuntimeException 발생 시 해당 메서드에서 처리
    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String, String>> handleException(RuntimeException e, HttpServletRequest request) {
        HttpHeaders responseHeaders = new HttpHeaders(); // 응답 헤더 생성
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST; // 400 Bad Request 상태 설정

        // 예외 발생 시 로그 기록
        LOGGER.error("Advice -> handleException: URI: {}, Error: {}", request.getRequestURI(), e.getMessage());

        // 에러 응답 생성
        Map<String, String> errorResponse = new HashMap<>();
        errorResponse.put("error type", httpStatus.getReasonPhrase()); // 에러 유형 설정 (Bad Request)
        errorResponse.put("code", "400"); // HTTP 상태 코드 설정
        errorResponse.put("message", e.getMessage()); // 예외 메시지 설정

        // 에러 응답 반환
        return new ResponseEntity<>(errorResponse, responseHeaders, httpStatus);
    }
}

 

  • 범위 지정
    • @RestControllerAdvice(basePackages = "com.springboot.valid_exception")로 특정 패키지 내의 컨트롤러 예외만 처리하도록 설정 가능.
  • 예외 클래스 지정
    • value 속성을 통해 처리할 예외 클래스를 지정 가능.
    • 예: @ExceptionHandler(value = RuntimeException.class)를 통해 RuntimeException을 처리.

 

이제 테스트 하기 위해 예외를 발생시킬 수 있는 컨트롤러를 생성하겠다.

@RestController
@RequestMapping("/exception")
public class ExceptionController {

    @GetMapping
    public void getRuntimeException() {
        throw new RuntimeException("getRuntimeException 메서드 호출");
    }
}

 

 

이에 대한 응답으로 ResponseBody에는 다음과 같은 응답이 담긴다.

{
  "error type": "Bad Request",
  "code": "400",
  "message": "getRuntimeException 메서드 호출"
}

 

 

3. 개별 컨트롤러 예외 처리

@RestControllerAdvice 또는 @ControllerAdvice는 별도의 범위 설정을 하지 않으면 전역 범위에서 모든 컨트롤러의 예외를 처리한다. 그러나, 특정 컨트롤러에서만 예외 처리를 하고 싶다면, 해당 컨트롤러 내부에 @ExceptionHandler 메서드를 생성하여 예외를 처리할 수 있다.

@RestController
@RequestMapping("/exception")
public class ExceptionController {

    private final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class);

    @GetMapping
    public void getRuntimeException() {
        // 예외 발생 메서드
        throw new RuntimeException("getRuntimeException 메서드 호출");
    }

    // @RestControllerAdvice에서 개별 컨트롤러로 예외 처리 이동
    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String, String>> handleException(RuntimeException e, HttpServletRequest request) {
        HttpHeaders responseHeaders = new HttpHeaders();
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

        // 개별 컨트롤러 내에서 로그 출력
        LOGGER.error("ExceptionController -> handleException: URI: {}, Error: {}", request.getRequestURI(), e.getMessage());

        // 에러 응답 생성
        Map<String, String> errorResponse = new HashMap<>();
        errorResponse.put("error type", httpStatus.getReasonPhrase());
        errorResponse.put("code", "400");
        errorResponse.put("message", e.getMessage());

        // 응답 반환
        return new ResponseEntity<>(errorResponse, responseHeaders, httpStatus);
    }
}

원래 @RestControllerAdvice에서 예외를 처리하던 부분을 개별 컨트롤러로 이동하여, 해당 컨트롤러에서 발생하는 예외만 처리하도록 했다.