[Spring Boot] @ControllerAdvice 와 @ExceptionHandler를 이용한 예외 처리

Monday·2024년 4월 8일
0

Spring

목록 보기
4/6
post-thumbnail

Checked Exception과 UnChecked Exception

Java 언어에서 제공하는 예외는 크게 Checked Exception 과 UnChecked Exception이 있다.

Checked Exception

java.lang.Exception 클래스를 상속받은 클래스다.
개발자가 try-catch 구문으로 감싸서 예외 처리를 하거나 메서드 시그니처에
throws 키워드를 사용하여 메서드 내부에서 발생하는 예외를 메서드를 호출하는 메서드로
다시 던져야 한다.

Unchecked Exception

java.lang.RuntimeException을 상속받는 Exception 클래스다.
Checked Exception은 예외 처리를 하지 않으면 컴파일 에러가 발생하지만,
Unchecked Exception은 예외 처리를
하지 않아도 컴파일 에러가 발생하지 않는다.
그래서 명시적으로 try-catch 구문이나 throws 키워드를 사용하여
예외 처리를 하지 않아도 된다.
하지만 이 예외는 스레드 호출 스택 기준으로 상위 객체의 메서드로 올라간다.
결국 발생한 Unchecked Exception도 어디선가 적절한 처리를 해야한다.

스프링 프레임워크에서 사용하는 대부분의 Exception 클래스는
RuntimeException을 상속받는 Unchekd Exception이다.

데이터베이스 관련 DataAccessExceptionRuntimeException을 상속 받고 있다.
데이터 베이스 관련된 예외도 개발자가 직접 예외 처리를 하기보다
프레임워크에서 제공하는 기능을 사용하여 일관된 방식으로 처리하도록 되어 있다.

비즈니스 로직을 처리하는 부분과 예외를 처리하는 부분이 분리되고,
개발자는 비즈니스 로직을 개발하는것에 더 집중할 수 있다.

스프링 프레임워크에서 예외를 처리하는 흐름

Service Layer 에서 로직을 수행하는 과정에 예외가 발생하는 경우
예외를 처리할 수 잇는 ApiExceptionHandler로 전달되고 개발자가 코딩한 로직을 처리한다.

@Service
public class Service {

	public long logic(long id) {
		repository.findById(id).orElseThrow(() -> {
        	log.error("Invalid number. Id : {}", id);
            
            return new BadRequestException("Not existing number");
        });    
    }
}

Id에 해당하는 결과를 조회할 수 없으면 BadRequestException을 던진다.
이때 Controller 클래스에 @ExceptionHandler 애너테이션이 선언된
예외 처리 메서드가 있다면, 생성된 예외를 예외 처리 메서드로 전달한다.

@RestController
public class Controller {
	
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleException(BadRequestException ex) {
    	//생략
    }
}

@ExceptionHandler 애너테이션은 @Controller 가 선언된 컨트롤러 클래스나 @ControllerAdvice가 선언된 컨트롤러 어드바이스 클래스에 사용할 수 있다.

@ControllerAdvice

@ControllerAdvice 는 스프링 애플리케이션 전체에서 예외 처리 메서드를 선언할 수 있는 특수한 스프링 빈이다.
별도의 클래스를 생성하고 클래스 선언부에 @ControllerAdvice 애너테이션을 선언하면
전역 설정 스프링 빈이 된다.
이 스프링 빈 내부에 @ExceptionHandler를 설정하면 애플리케이션 전체에
예외 처리 메서드가 동작한다.

물론 컨트롤러 클래스마다 예외를 각자 다르게 처리하여 예외 처리를 세분하고 싶다면 @ExceptionHandler 애너테이션을 모든 컨트롤러에 선언하자.
하지만 시스템 유지 보수 과정이나 신규 컨트롤러 클래스를 추가하는 과정에서
누락될 가능성이 있다.
그래서 @ControllerAdvice를 사용하여 애플리케이션을 전역 설정할 때가 많다.

@RestControllerAdvice

@RestControllerAdvice@ControllerAdvice@ResponseEntity 기능을 합친 애너테이션이다.

그러므로 @ExceptionHandler 가 정의된 예외 처리 메서드가 리턴하는 객체는 HttpMessageConverter로 마셜링된다.
그리고 변경된 JSON 메시지가 클라이언트에 전달된다.
즉 예외 처리를 하면서 동시에 에러 메시지를 클라이언트에 응답할 수 있다.

@RestControllerAdvice@ExceptionHandler 애너테이션을 사용하면 애플리케이션에서 발생한 예외도 처리하면서 동시에 서비스 클래스에서 발생한 검증 예외도 일관된 방법으로 처리할 수 있다.

CustomExcepton

RuntimeException을 상속하여 CustomException 을 정의하면
서비스의 특정한 예외 케이스를 잘 정의할 수 있다.

public class CustomException extends RuntimeException {

	private String errorMessage;
    
    public CustomException(String errorMessage) {
    	super();
        this.errorMessage = errorMessage;
    }
    
    public String getErrorMessage() {
    	return errorMessage;
    }
}

errorMessage 는 클라이언트에 전달할 목적으로 사용한다.
최상위 부모 클래스 Throwable.classmessage 속성과는 다른 용도다.

즉, 어던 클래스에서도 CustomException 예외를 생성하면 클라이언트에 전달할 메시지를
직접 설정할 수 있다.

@RestControllerAdvice, @ExceptionHanlder 애너테이션을 같이 사용하면 훌륭한 예외 처리 매커니즘이 된다.

전역 예외 처리

@RestControllerAdvice
public class ApiExceptionHandler {
	
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ErrorReponse> handleCustomException(CustomException ex) {
    	System.out.println("Error Message : " + ex.getErrorMessage());
        
        return new ResponseEntity<>(
        	new ErrorResponse(ex.getErrorMessage()),
            HttpStatus.BAD_REQUEST
        );
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
    	
        return new ResponseEntity<>(
        	new ErrorResponse("system error"),
            HttpStatus.INTERNAL_SERVER_ERROR
        );
    }
}

CustomException 외의 나머지 예외를 처리하기 위해
handleException() 메서드 처럼 다양한 예외 상황에 대비할 수 잇는 폴백 기능을 추가하자.
@ExceptionHandlerException.class 가 설정되어 있다.

즉, 시스템 내부에서 발생할 수 있는 모든 예외의 상위 클래스를 설정하였다.
이렇게 되면 CustomException을 제외한 모든 예외는 handleException() 메서드가
처리할 수 있다.

그래서 개발자가 미리 대응할 수 없는 여러 상황을 처리할 수 있다는 장점이 있다.
또한 최소한 REST-API를 호출한 클라이언트에 잘못된 메시지가 전달되거나 시스템 내부 정보가 노출되는 것을 사전에 막을 수 있다.

@ControllerAdvice@ExceptionHandler 외에도 @InitBinder@ModelAttribute 애너테이션 설정을 애플리케이션 전체에 설정하여 사용할 수 있다.

출처
스프링 부트로 개발하는 MSA 컴포넌트

profile
차근차근 꾸준히

0개의 댓글