JeongJin's Blog

10. 유효성 검사와 예외처리(3) 본문

Book Study/스프링 부트 핵심 가이드

10. 유효성 검사와 예외처리(3)

정진킴 2023. 11. 27. 09:00

10.4 예외처리

10.4.1 예외 처리

  • 프로그래밍에서 예외(exception)란 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황을 의미한다.
  • 예외는 개발자가 직접 처리 할 수 있는 것이므로 미리 코드 설계를 통해 처리할 수 있다.
  • 에러 vs 예외
    • 에러는 주로 자바의 가상머신에서 발생시키는 것으로서 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없다.
    • 대표적인 예로 메모리 부족(OutOfMemory), 스택 오버플로우(StackOverFlow) 등이 있음
    • 이러한 에러는 발생 시점에 처리하는 것이 아니라 미리 애플리케이션의 코드를 살펴보면서 문제가 발생하지 않도록 예방해서 원천적으로 차단해야 한다.

10.4.2 예외 클래스

  • 모든 예외는 Throwable 클래스를 상속받습니다. 그리고 가장 익숙하게 볼 수 있는 Exception 클래스는 다양한 자식 클래스를 가지고 있습니다. 해당 클래스는 크게 Checked Exception과 Unchecked Exception으로 구분할 수 있다.

  • Checked Exception은 컴파일 단계에서 확인 가능한 예외 상황으로 IDE에서 캐치해서 반드시 예외 처리를 할 수 있게 표시해준다.
  • Unchecked Exception은 런타임 단계에서 확인되는 예외 상황을 나타냅니다. 문법상 문제는 없지만 프로그램이 동작하는 도중 예기치 않은 상황이 생겨 발생하는 예외를 의미한다.

10.4.3 예외 처리 방법

예외가 발생했을 때 처리하는 방법은 크게 세 가지가 존재한다.

  • 예외 복구
    • 예외 상황을 파악해서 문제를 해결하는 방식
    • 대표적인 방법 try/catch 구문
  • 예외 처리 회피
    • 예외가 발생한 시점에서 바로 처리하는 것이 아니라 예외가 발생한 메서드를 호출한 곳에서 예외 처리를 할 수 있게 전가하는 방식
    • throw 키워드를 사용해 어떤 예외가 발생했는지 호출부에서 내용을 전달 할 수 있다.
int a = 1;
String b = "a";

try {
	System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e) {
	throw new NumberFormatException("숫자가 아닙니다");
}
  • 예외 전환
    • 앞의 두 방식을 적절하게 섞은 방식

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

  • 예외가 발생했을 때 클라이언트에 오류 메세지를 전달할려면 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 한다. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 두 가지가 있다.
    • @(Rest)ControllerAdvice 와 @ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리
      • @ControllerAdvice 대신 @RestControllerAdvice 를 사용하면 결과값을 JSON 형태로 반환할 수 있다.
    • @ExceptionHandler를 통해 특정 컨트롤러의 예외를 처리
@RestControllerAdvice
public class GlobalException {
	@ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String, String>> handlerException(RuntimeException e,
    	HttpServletRequest request) {
    	HttpHeaders responseHeaders = new HttpHeaders();
        ...
    }

}
  • @(Rest)ControllerAdvice 는 스프링에서 제공하는 어노테이션으로 @(Rest)Controller 에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 기능을 수행한다.
    • Service 소스에서 throw new 호출해도 Controlle에서는 null로 출력므로 해당 어노테이션을 사용한다.
  • @ExceptionHandler는 @(Rest)Controller 가 적용된 Bean 에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용
    • 어떤 예외 클래스를 처리할지는 value 속성으로 등록한다.
    • value 속성은 배열의 형식으로도 전달받을 수 있어 예러 예외 클래스를 등록할 수 있다.
  • 해당 설정을 통해 예외를 관제하는 범위를 지정할 수 있다.
    • basePackages 에 정의된 package만 예외를 관제한다.
@RestControllerAdvice(basePackages="com.springboot.valid_exception")

 

10.4.5 커스텀 예외

  • 표준 예외에서 제공하는 클래스는 해당 예외 타입의 이름만으로 이해하기 어려운 경우가 있으므로, 표준 예외를 사용할 때는 예외 메세지를 상세히 작성해야 하는 번거로움이 있다.
  • 애플리케이션에서 발생하는 예외를 개발자가 직접 관리하기가 수월하다.

10.4.6 커스텀 예외 클래스 생성하기

  • 커스텀 예외는 예외가 발생하는 상황에 해당하는 상위 예외 클래스를 상속받는다. 그래서 커스텀 예외는 상위 예외 클래스보다 좀 더 구체적인 이름을 사용하기도 한다.
// AccountException.java
public class AccountException extends RuntimeException {
    private final ErrorCode errorCode;
    private final String errorMessage;
    
    public AccountException(ErrorCode errorCode) {
    	this.errorCode = errorCode;
        this.errorMessage = errorCode.getDescription();
    }
}

// ErrorCode.java
@Getter
@AllArgsConstructor
public enum ErrorCode {
	INVALID_SERVER_ERROR("내부 서버 오류가 발생하였습니다."),
	INVALID_REQUEST("잘못된 요청입니다."),
	USER_NOT_FOUND("사용자가 없습니다."),
	ACCOUNT_NOT_FOUND("계좌가 없습니다."),
	ACCOUNT_TRANSACTION_LOCK("해당 계좌는 사용 중입니다."),
	TRANSACTION_NOT_FOUND("해당 거래가 없습니다."),
	AMOUNT_EXCEED_BALANCE("거래 금액이 계좌 잔액보다 큽니다."),
	TRANSACTION_ACCOUNT_UN_MATCH("이 거래는 해당 계좌에서 발생한 거래가 아닙니다."),
	CANCEL_MUST_FULLY("부분 취소는 허용되지 않습니다."),
	TOO_OLD_ORDER_TO_CANCEL("1년이 지난 거래는 취소가 불가능합니다."),
	USER_ACCOUNT_UNMATCH("사용자와 계좌의 소유주가 다릅니다."),
	ACCOUNT_ALREADY_UNREGISTERED("계좌가 이미 해지되었습니다."),
	BALANCE_NOT_EMPTY("잔액이 있는 계좌는 해지할 수 없습니다."),
	MAX_COUNT_PER_USER_10("사용자 최대 계좌는 10개입니다.")
	;

	private String description;
}

// 응답 메세지
{
    "errorCode": "ACCOUNT_NOT_FOUND",
    "errorMessage": "계좌가 없습니다."
}