SpringBoot

예외 처리

똑똑한망치 2024. 2. 28. 23:40
728x90
반응형

1. 예외 처리


프로그래밍에서 예외(exception)란 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황이다. 예외는 개발자가 직접 처리할수 있으므로 미리 코드 설계를 통해 처리할 수 있다.

 

에러(error)는 주로 자바의 가상머신에서 발생시키는 것으로 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없다. 예를 들어 메모리 부족(OutOfMemory), 스택 오버플로(StackOverFlow)가 있다.

 

 

(1) 예외 클래스

자바의 예외 클래스는 아래와 같은 상속 구조를 갖추고 있다.

자바의 예외 클래스의 상속 구조

 

Exception 클래스는 Checked Exception 과 Unchecked Exception 으로 구분할 수 있다.

 

  Checked Exception
(컴파일 단계에서 확인 가능한 예외 상황)
Unchecked Exception
(문법상 문제는 없지만 프로그램이 동작하는 도중 예기치 않은 상황이 생겨 발생하는 예외)
처리 여부 반드시 예외 처리 필요 명시적 처리를 강제하지 않음
확인 시점 컴파일 단계 실행 중 단계
대표적인 예외 클래스 IOException
SQLException
RuntimeException
NullPointerException
IllegalArgumentException
IndexOutOfBoundException
SystemException

 

 

 

 

(2) 예외 처리 방법

예외가 발생했을 때 이를 처리하는 방법은 예외 복구, 예외 처리 회피, 예외 전환 3가지가 있다.

 

1. 예외 복구

예외 상황을 파악해서 문제를 해결하는 방식이다.

대표적인 예시가 try/catch 구문이다.

 

2. 예외 처리 회피

예외가 발생한 시점에서 바로 처리하는 것이 아니라 예외가 발생한 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식이다. 이때 throw 키워드를 사용해 어떤 예외가 발생했는지 호출부에 내용을 전달할 수 있다.

 

3. 예외 전환

예외가 발생했을 때 어떤 예외가 발생했느냐에 따라 호출부로 예외 내용을 전달하면서 좀 더 적합한 예외 타입으로 전달할 필요가 있다. 또는 애플리케이션에서 예외 처리를 좀더 단순하게 하기 위해 래핑(wrapping)해야 하는 경우도 있다. 이런 경우 try/catch 방식을 사용하면서 catch 블록에서 throw 키워드를 사용해 다른 예외 타입으로 전달하면 된다.

 

 

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

예외가 발생했을 때 클라이언트에 오류 메시지를 전달하려면 각 레이어에서 발생한 예외를 엔트포인트 레벨인 컨트롤러로 전달해야 한다. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 2가지가 있다.

 

1. @(Rest)ControllerAdvice와 @ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리

 

@RestControllerAdvice
public class CustomExceptionHandler {
	private final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionHandler.class);
    
    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String, String>> handlerException(
    	RuntimeException e, HttpServletRequest request
    ) {
    	HttpHeaders responseHeaders = new HttpHeaders();
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        
        LOGGER.error("Advice 내 handlerException 호출, {}, {}",
        	request.getRequestURI(), e.getMessage());
        
        ...
        // (생략)
        
    }
}

 

@RestcontrollerAdvice 와 @ControllerAdvice는 스프링에서 제공하는 어노테이션이다. @Controller 나 @RestController에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 기능을 수행한다.

@ExceptionHandler는 @Controller나 @RestController 가 적용된 빈에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용한다. value 속성으로 어떤 예외 클래스를 처리할지 등록한다.

 

 

 

2. @ExceptionHandler를 통해 특정 컨트롤러의 예외를 처리

@(Rest)ControllerAdvice 는 별도 범위 설정이 없으면 전역 범위에서 예외를 처리한다. 

따라서 특정 컨트롤러에서만 동작하는 @ExceptionHandler 메서드를 생성해서 처리할 수도 있다.

컨트롤러 클래스 내부에 handleException() 을 생성한다. 즉, 해당 클래스에 국한해서 예외 처리를 할 수 있다.

 

@RestController
@RequestMapping("/exception")
public class ExceptionController {
	private final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class);
    
    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String,String>> handleException(
    	RuntimeException e, HttpServletRequest request
    ) {
    	HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpStatus httpstatus = HttpStatus.BAD_REQUEST;
        
        ...
        // (생략)
    }
}

 

만약, @ControllerAdvice와 컨트롤러 내에 동일한 예외 타입을 처리한다면 좀 더 우선순위가 높은 클래스 내의 핸들러 메서드가 사용되는 것을 볼 수 있다.

 

예외 타입 레벨에 따른 예외 처리 우선순위
핸들러 위치에 따른 예외 처리 우선순위

 

 

 

 

(3) 커스텀 예외

커스텀 에외는 만드는 목적에 따라 생성하는 방법이 다르다.

커스텀 예외 클래스를 생성하는데 필요한 내용은 3가지 이다.

  • 에러 타입 (error type) : HttpStatus 의 reasonPhrase
  • 에러 코드 (error code) : HttpStatus 의 value
  • 메시지 (message) : 상황별 상세 메시지

커스텀 예외 클래스 구조

 

반응형