Spring MVC에서의 예외처리(3) - 비즈니스 로직에 대한 예외처리

Backend kwon·2023년 8월 26일
0

Java에서는 throw 키워드를 사용해서 예외를 메서드 바깥으로 던질 수 있습니다.
던져진 예외는 메서드 바깥 즉, 메서드를 호출한 지점으로 던져지게 되는 것입니다.

서비스 계층의 메서드는 API 계층인 Controller의 핸들러 메서드가 이용하므로 서비스 계층에서 던져진 예외는 Controller의 핸들러 메서드 쪽에서 잡아서 처리할 수 있습니다.

그런데 이미 전에 Controller에서 발생하는 예외를 Exception Advice에서 처리하도록 공통화해두었으니 서비스 계층에서 던진 예외 역시 Exception Advice에서 처리하면 됩니다.

 

서비스 계층에서 예외 던지기(throw)

@Service
public class MemberService {
    ...
		...

    public Member findMember(long memberId) {
        // TODO should business logic
				
				// (1)
        throw new RuntimeException("Not found member");
    }

		...
		...
}

throw 키워드를 사용하여 RuntimeException 객체에 적절한 예외 메시지를 포함한 후에 메서드 밖으로 던졌습니다.

 

GlobalExceptionAdvice 예외 잡기(catch)

@RestControllerAdvice
public class GlobalExceptionAdvice {
    ...
		...
		
		// (1)
    @ExceptionHandler
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
        System.out.println(e.getMessage());

				// 수정 필요

        return null;
    }
}

RuntimeException을 잡아서(catch) 처리하기 위한 handleResourceNotFoundException() 메서드를 추가했습니다.

MemberController의 getMember() 핸들러 메서드에 요청을 보내면 MemberService에서 RuntimeException을 던지고, GlobalExceptionAdvice의 handleResourceNotFoundException() 메서드가 이 RuntimeException을 잡아서 예외 메시지인 “Not found member”를 콘솔에 출력할 것입니다.

서비스 계층에서 RuntimeException을 그대로 던지고(throw), Exception Advice에서 RuntimeException을 그대로 잡는 것(catch)은 예외의 의도가 명확하지 않으며, 구체적으로 어떤 예외가 발생했는지에 대한 예외 정보를 얻는 것이 어렵습니다.

그래서 사용자 정의 예외를 사용해야 합니다.

 

사용자 정의 예외(Custom Exception) 사용

  • 예외코드 정의
public enum ExceptionCode {
    MEMBER_NOT_FOUND(404, "Member Not Found");

    @Getter
    private int status;

    @Getter
    private String message;

    ExceptionCode(int status, String message) {
        this.status = status;
        this.message = message;
    }
}

ExceptionCode를 enum으로 정의하면 비즈니스 로직에서 발생하는 다양한 유형의 예외를 enum에 추가해서 사용할 수 있습니다.

 

  • BusinessLogicException 구현
public class BusinessLogicException extends RuntimeException {
    @Getter
    private ExceptionCode exceptionCode;

    public BusinessLogicException(ExceptionCode exceptionCode) {
        super(exceptionCode.getMessage());
        this.exceptionCode = exceptionCode;
    }
}

서비스 계층에서 사용할 BusinessLogicException이라는 Custom Exception을 정의합니다.

BusinessLogicException은 RuntimeException을 상속하고 있으며 ExceptionCode를 멤버 변수로 지정하여 생성자를 통해서 조금 더 구체적인 예외 정보들을 제공해 줄 수 있습니다.

그리고 상위 클래스인 RuntimeException의 생성자(super)로 예외 메시지를 전달해 줍니다.

BusinessLogicException은 서비스 계층에서 개발자가 의도적으로 예외를 던져야 하는 다양한 상황에서 ExceptionCode 정보만 바꿔가며 던질 수 있습니다.

 

  • 서비스 계층에 BusinessLogicException 적용
@Service
public class MemberService {
    ...
		...

    public Member findMember(long memberId) {
        // TODO should business logic

				// (1)
        throw new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND);
    }

    ...
		...
}

 

  • Exception Advice에서 BusinessLogicException 처리
@RestControllerAdvice
public class GlobalExceptionAdvice {
    ...
		...

    @ExceptionHandler
    public ResponseEntity handleBusinessLogicException(BusinessLogicException e) {
        System.out.println(e.getExceptionCode().getStatus());
        System.out.println(e.getMessage());

				// ErrorResponse 수정

        return new ResponseEntity<>(HttpStatus.valueOf(e.getExceptionCode().getStatus()));
    }
}

메서드 명 변경
: 먼저 메서드 명이 서비스 계층의 비즈니스 로직 처리에서 발생하는 예외를 처리하는 것을 목적으로 하기 때문에 메서드 명이 handleBusinessLogicException으로 변경되었습니다.

메서드 파라미터 변경
: RuntimeException을 파라미터로 전달받던 것을 BusinessLogicException을 전달받는 것으로 변경되었습니다.

@ResponseStatus(HttpStatus.NOT_FOUND) 제거
: @ResponseStatus 애너테이션은 고정된 HttpStatus를 지정하기 때문에 BusinessLogicException과 같이 다양한 Status를 동적으로 처리할 수 없으므로 ResponseEntity를 사용해서 HttpStatus를 동적으로 지정하도록 변경했습니다.

한 가지 유형으로 고정된 예외를 처리할 경우에는 @ResponseStatus로 HttpStatus를 지정해서 사용하면 되고, BusinessLogicException처럼 다양한 유형의 Custom Exception을 처리하고자 할 경우에는 ResponseEntity를 사용하면 됩니다.

profile
백엔드개발자를 향해서

0개의 댓글