[Spring] Spring boot 예외처리

WOOK JONG KIM·2022년 11월 7일
0
post-thumbnail
post-custom-banner

예외와 에러

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

에러는 주로 자바의 가상머신에서 발생시키는 것으로 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없음
-> 발생 시점에 처리하는 것이 아니라 미리 애플리케이션의 코드를 보며 원천적으로 차단해야 함

모든 예외클래스는 Throwable 클래스 상속 받음

CheckedException은 컴파일 단계에서 확인 가능한 예외 상황
ex) IOException, SQLException

Unchecked Exception은 런타임 단계에서 확인되는 예외상황
-> 문법상 문제는 없지만 프로그램 동작중에 발생하는 도중 예기치 않은 상황이 생겨 발생하는 예외 의미
ex) RuntimeException, NullPointerException, IllegalArgumentException, IndexOutofBoundException, SystemException


자바의 예외 처리 방법

방법엔 크게 3가지

  1. 예외 복구
  2. 예외 처리 회피
  3. 예외 전환

예외 복구 방법은 예외 상황을 파악해서 문제를 해결하는 방식
-> 대표적으로 try/catch

int a = 1;
String b = "a";

try{
	System.out.println(a + Integer.parseInt(b));
} catch(NumberFormatException e){
	b = "2";
    System.out.println(a + Integer.parseInt(b));
}

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

int a = 1;
String b = "a";

try{
	System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e){
	throw new NumberFormatException("숫자가 아닙니다.");
}

예외 전환은 앞 두 방식을 섞은 방식


스프링 부트 예외 처리 방식

웹 서비스 애플리케이션에서는 외부에서 들어오는 요청에 담긴 데이터를 처리하는 경우 많음
-> 예외 발생시 이를 복구하여 정상 처리하기 보단 요청을 보낸 클라이언트에 어떤 문제가 발생했는지 상황을 전달하는 경우가 많음

예외가 발생했을 때 클라이언트에 오류 메세지를 전달하려면 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 함

스프링 부트에서 예외를 처리하는 방식은 크게 두가지

  • @(Rest)ControllerAdvice@ExceptionHandler를 통해 모든 컨트롤러의 예외 처리
  • @ExceptionHandler를 통해 특정 컨트롤러의 예외 처리

RestControllerAdvice를 사용하면 결과값을 JSON 형태로 반환 가능

CustomExceptionHandler 클래스

@RestControllerAdvice
public class CustomExceptionHandler {

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

    @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("Advice 내 handleException 호출, {}, {}", request.getRequestURI(),
                e.getMessage());

        Map<String, String> map = new HashMap<>();
        map.put("error type", httpStatus.getReasonPhrase());
        map.put("code", "400");
        map.put("message", e.getMessage());

        return new ResponseEntity<>(map, responseHeaders, httpStatus);
    }
}

@Restcontroller는 @Controller나 @RestController에서 발생하는 예외를 한 곳에서 편하게 관리할 수 있게 하는 기능 수행

예외를 관제하는 범위 지정 가능

@RestControllerAdvice(basePackages = "com.springboot.valid_exception")

@ExceptionHandler는 @Controller나 @Restcontroller가 적용된 빈에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용

어떤 예외 클래스를 처리할지는 value 속성으로 등록(배열 형식으로 여러 예외 클래스 등록 가능)

위 코드에서는 RunTimeException에 포함되는 각종 예외가 발생하는 경우 포착해서 처리
-> 메서드 내에는 클라이언트에게 오류가 발생했음을 알리는 응답 메세지 구성하여 리턴 하였음

예외를 발생시킬 컨트롤러 생성(ExceptionController)

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

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

예외 발생 시켰을 때

핸들러 메서드가 위와 같은 응답을 출력

이 처럼 컨트롤러에서 던진 예외는 @RestControllerAdivce가 선언돼 있는 핸들러 클래스에서 매핑된 예외 타입을 찾아서 처리
-> @ControllerAdvice 및 @RestControllerAdvice는 전역 범위에서 예외 처리를 하기 때문에 특정 컨트롤러에서만 동작하는 @ExceptionHandler 메서드를 생성해 처리할 수도 있다

Exception컨트롤러에 메서드 추가

@RestController
@RequestMapping("/exception")
public class ExceptionController {
    
    private final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class)

    @GetMapping
    public void getRuntimeException(){
        throw new RuntimeException("getRuntimeException 메서드 호출");
    }
    
    @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;
        
        LOGGER.error("클래스 내 handleException 호출, {}, {}", request.getRequestURI(),
                e.getMessage());
        
        Map<String, String> map = new HashMap<>();
        map.put("error type", httpStatus.getReasonPhrase());
        map.put("code", "400");
        map.put("message", e.getMessage());
        
        return new ResponseEntity<>(map, responseHeaders, httpStatus);
    }
}

컨트롤러 클래스 내에 @ExceptionHandler 어노테이션을 사용한 메서드 선언 시 해당 클래스에 국한하여 예외 처리 가능

위 경우 예외를 다시 발생시킬시

com.springboot.valid_exception.controller.ExceptionController 클래스 내 handleException 호출, /exception, getRuntimeException 메서드 호출

@ControllerAdvice의 ExceptionHandler와 컨트롤러 내의 ExceptionHandler가 동일한 예외 타입을 처리한다면 좀더 우선순위가 높은 클래스의 핸들러 메서드가 사용됨

예를 들어

@ExceptionHandler(Exception.class) 와 @ExceptionHandler(NullPointerException.class)의 메서드가 있다면 좀더 구체적인 NullPointerException의 핸들러가 우선순위 가짐

그리고 ControllerAdvice()내 ExceptionHandler보단 컨트롤러 내 ExceptionHandler가 우선순위를 가지게 된다

profile
Journey for Backend Developer
post-custom-banner

0개의 댓글