본문 바로가기
Spring

[Spring] 서비스 계층 예외 던지기(throw) 및 예외 처리 (+ Custom Exception 생성)

by jungha_k 2022. 10. 26.

나잡아봐라

 

체크 예외와 언체크 예외?  (Checked Exception vs Unchecked Exception)

: 애플리케이션에서 발생하는 예외는 크게 둘로 나뉜다.

 

 

체크 예외 : 발생한 예외를 잡아서(catch) 체크한 후 구체적인 처리가 요구되는 예외 (예외 복구, 회피 등)

ex) ClassNotFoundException

 

 

언체크 예외 : 예외를 잡아서 해당 예외에 대한 처리가 필요 없는 예외

ex) NullPointerException, ArrayIndexOutOfBoundsException

*주로 개발자의 코드 작성 오류로 발생되는데,

모두 RuntimeException을 상속한 예외들이다. (Java, Spring 지원)

이 RuntimeException은 너무 방대하므로 때때로 개발자가 직접 예외를 만들어야 할 경우도 있다!

 


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

 

 

1. 백엔드 서버 ↔ 외부 시스템 연동에서 발생하는 에러 처리

 

백엔드 서버 쪽에서 해결해줄 수 있는게 없는 경우

 

ex) 암호 화폐 지갑 case, 코인 전송 시에 코인 잔고가 부족할 경우?

클라이언트가 지갑에 잔고를 채우는게 최선의 방법!

so, 백엔드 서버 쪽에서 예외를 의도적으로 던져서 에러 발생 정보를 알려준다! 

 

 

2. 시스템 내부에서 조회하려는 리소스(자원)이 없는 경우

 

db에 요청받은 해당 정보가 없을 경우에

서비스 계층에서 해당 정보가 없다는 예외를 의도적으로 전송해서

클라이언트 쪽에 알려준다!

 


의도적인 예외 던지기/받기 (throw/catch)

 

 

Java의 경우 throw 키워드를 이용해서 예외를 메서드 바깥으로 던진다.

던져진 예외는 메서드 호출 지점으로 던져짐!

 

스프링 서비스 계층에서 예외를 던질 경우?

 

서비스 계층의 메서드 : Controller 핸들러 메서드가 이용한다.

so, 서비스 계층에서 던져진 예외 : Controller의 핸들러 메서드 쪽에서 잡아서 처리 가능하다.

 


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

 

 

코드 변경 흐름 위주로 정리

 

서비스 계층의 예외는 컨트롤러의 핸들러 메서드 쪽에서 잡아서 처리 가능하다.

그러나 이미 Controller의 예외를 모두 받아서 처리하는 클래스를 만들었었고,

그게 바로 @RestControllerAdvice 가 달린 GlobalExceptionAdvice 클래스!

 

🔽

@Service 계층에서 의도적으로 예외를 발생시킬 메서드 안

Exception throw할 코드를 작성했다. 

//findMember 메서드안에 포함된 코드
throw new RuntimeException("Not found member")

 

이렇게 던져진 RuntimeException은 GlobalExceptionAdvice에서 받게된다.

받게될 메서드는 

 

🔽


새로 @ExceptionHandler 작성해서 추가를 해주고,
RuntimeException을 매개변수로 받아 이의 메세지를 뱉도록 해주었다.

@ExceptionHandler
@ResponStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
        System.out.println(e.getMessage());
       }

 

이렇게 추가를 해준 것이 끝!

Postman으로 MemberController의 getMember() 핸들러 메서드에 요청을 보내면,

MemberService에서 RuntimeException을 던지고,

 

 

GlobalExceptionAdvice의 handleResourceNotFoundException() 메서드가

이 RuntimeException을 잡아서

 

예외 메시지인 “Not found member”가 콘솔에 출력된다.

 


그렇지만, 이렇게 예외 처리를 하게 된다면

본래 예외를 작성하는 의도와는 달라지게 된다.

 

예외를 던지게 될 경우가 무척 많을텐데
추상적인 runtimeException을 그대로 던지는 것은 바람직하지 않다.


우리는 예외가 언제 일어나는지 알고 싶어

예외를 던지는 것이기 때문에.. 조금 더 구체적인 예외 정보가 필요하다.

 


🔽


사용자 정의 예외 사용

따라서 추상적인 runtimeException 보다 

예외를 구체적으로 표현할 수 있는 Custom Exception을 만들어준다.

 

🔽


ExceptionCode를 enum으로 작성하고,

MEMBER_NOT_FOUND(404, "Member Not Found");


이 ExceptionCode의 메세지를 사용하는 BusinessLogicException 코드를 생성했다.
RuntimeException을 extend 받으며,
exceptionCode도 생성자로 생성해줬다. (멤버변수로 지정)

상위 클래스(RuntimeException)의 생성자로 예외 메세지 전달해준다.

super(exceptionCode.getMessage());

 

🔽


이렇게 BusinessLogicException 클래스가 완성된다면

이를 원래 예외를 받아 처리하는 GlobalExceptionAdvice에 

@ExceptionHandler를 달아 처리해주면 끝!

 


* @HttpStatus 정해진 메세지만을 돌려줄 수 있기에

(ex) HttpStatus.BAD_REQUEST, HttpStatus.NOT_FOUND)


"Member Not Found"처럼 내가 커스텀한 메세지를 사용하고 싶다면
ResponEntity 활용해 HttpStatus를 동적으로 지정해준다.

 


* @ResponseStatus  vs  ResponseEntity 

 

@ResponseStatus : 고정된 예외 처리시

ResponseEntity : 다양한 유형의 Custom Exception 처리시


최종 코드 기반 정리 

 


1. enum인 ExceptionCode를 생성해줌.. 커스텀 메세지와 함께


2. ExceptionCode를 멤버 변수로 쓰는 (생성자 주입해서)
    BusinessLogicException 만듬...runtimeException을 extend

3. @service 코드에 BusinessLogicException 예외 던져줄 것(throw) 작성함

 

4. 어차피 Controller에서 날려준 모든 예외들은 GlobalExceptionAdvice에서 받기 때문에
여기서 @Exceptionhandler 달아서 추가한다.  


콘솔에 어떻게 메세지 남길지, HttpStatus를 어떻게 남길지
getStatus()나 getMessage 등을 이용해서 ResponseEntity에 담아서 출력 

5. 해당 예외가 일어날 경우 콘솔에 잘 찍힌다!

댓글