[Section3] Spring MVC - 비즈니스 로직에 대한 예외처리

2023. 6. 15. 13:59

🧑🏻‍💻 TIL(Today I Learned)


✔️ checked Exception, unchecked Exception

 

💡 체크 예외와 언체크 예외 

➡️ 애플리케이션에서 발생하는 예외는 체크 예외와 언체크 예외로 구분

  • 체크 예외(Checked Exception)
    : 발생한 예외를 잡아서(catch) 체크한 후에 해당 예외를 복구하든가 아니면 회피하든가 등의 어떤 구체적인 처리를 해야하는 예외 
    : ex)  ClassNotFoundException

  • 언체크 예외(Unchecked Exception)
    : 예외를 잡아서(catch) 해당 예외에 대한 어떤 처리를 할 필요가 없는 예외 
    : 명시적으로 잡아서 어떤 처리를 할 필요가 없음
    : ex) NullPointerException, ArrayIndexOutOfBoundsException

 

🔎 개발자가 의도적으로 예외를 던질 수(throw) 있는 상황

  • 백엔드 서버와 외부 시스템과의 연동에서 발생하는 에러 처리
  • 시스템 내부에서 조회하려는 리소스(자원, Resource)가 없는 경우 

 

💡 서비스 계층에서 예외 던지기

➡️ Java에서 던져진 예외는 메서드 바깥 즉, 메서드를 호출한 지점으로 던져짐

➡️서비스 계층의 메서드는 API 계층인 Controller의 핸들러 메서드가 이용하기 때문에 서비스 계층에서 던져진 예외는 Controller의 핸들러 메서드 쪽에서 잡아서 처리할 수 있음 

➡️ 전날 실습에서 Controller에서 발생한 예외를 Exception Advice에서 처리하도록 공통화해두었으니 서비스 계층에서 던진 예외 또한 여기서 처리

@Service
public class MemberService {
		...

    public Member findMember(long memberId) {
        // TODO should business logic
				
				// (1)
        throw new RuntimeException("Not found member");
    }
		...
}
  • 서비스 계층에서 예외 던지기
    → 회원 정보 조회를 요청했으나 조회되는 회원이 없다고 가정하고 메서드 밖으로 던지기 
@RestControllerAdvice
public class GlobalExceptionAdvice {
    ...
		...
		
		// (1)
    @ExceptionHandler
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
        System.out.println(e.getMessage());

        return null;
    }
}
  • 서비스 계층에서 던져진 예외를 GlobalExceptionAdvice 클래스에서 받기

➡️ 하지만 서비스 계층에서 의도적으로 던질 수 있는 예외 상황은 회원 정보가 존재하지 않는 경우뿐만 아니라 다양한 하게 존재하기 때문에 메서드 이름은 적절하지 않음 

➡️ RuntimeException을 그대로 던지고 Exception Advice에서  RuntimeException을 그대로 잡는 것은 예외의 의도가 명확하지 않으며 구체적으로 어떤 예외가 발생했는지에 대한 예외 정보를 얻는 것이 어려움

 

 

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

➡️ RuntimeException과 같은 추상적인 예외가 아닌 좀 더 구체적으로 표현할 수 있는 Custom Exception을 만들어서 예외를 던질 수 있음

import lombok.Getter;

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;
    }
}
  • Custom Exception에 사용할 ExceptionCode를 enum으로 정의 
  • ExceptionCode를 enum으로 정의하면 비즈니스 로직에서 발생하는 다양한 유형의 예외를 enum에 추가해서 사용 가능

 

import lombok.Getter;

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

    public BusinessLogicException(ExceptionCode exceptionCode) {
        super(exceptionCode.getMessage());
        this.exceptionCode = exceptionCode;
    }
}
  • 서비스 계층에서 사용할 Custom Exception 정의 
  • RuntimeException을 상속하고 있으며 ExceptionCode를 멤버 변수로 지정하여 생성자를 통해서 더 구체적인 예외 정보 제공 가능
  • 서비스 계층에서 개발자가 의도적으로 예외를 던져야 하는 다양한 상황에서 Exception Code 정보만 바꿔가며 던질 수 있음
@Service
public class MemberService {
    ...
		...

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

				// (1)
        throw new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND);
    }
}
  • 서비스 계층에 BusinessLogicException 적용
  • 조금 더 구체적인 예외 정보를 던지도록 변경 

 

@RestControllerAdvice
public class GlobalExceptionAdvice {
		...

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

        return new ResponseEntity<>(HttpStatus.valueOf(e.getExceptionCode().getStatus()));
    }
  • BusinessLogicException을 처리하도록 수정된 GlobalExceptionAdvice
    •  메서드명 변경
    • 메서드 파라미터 변경 
    • @ResponseStatus 제거
      → 이 애너테이션은 고정된 HttpStatus를 지정하기 때문에 동적으로 지정할 수 있도록 ResponseEntity로 변경

➡️ 코드 수정이 다 끝나고 Postman에서 MemberController의 getMember() 메서드에 전송 요청하면 아래와 같은 결과가 콘솔에 나옴

    ( Response 응답 메세지를 작성하지 않아서 포스트맨에는 아무것도 나오지 않음, 콘솔에만 찍힘)

BELATED ARTICLES

more