예외처리 (2)

 

 

 

 

비즈니스 로직은 서비스 계층에 있다.

 

 

 

비즈니스 로직 예외 던지기(throw) 및 예외 처리

 

 

➤ 체크 예외(Checked Exception)와 언체크 예외(Unchecked Exception)

 

애플리케이션에서 발생하는 예외는 크게 Checked ExceptionUnchecked Exception으로 구분된다.

 

▶️ Checked Exception

  • 발생한 예외를 잡아서(catch) 체크한 후 해당 예외를 복구하거나 회피하거나 하는 등의 구체적인 처리를 해야하는 예외이다.
  • 대표적인 예 : ClassNotFoundException

 

 

▶️ Unchecked Exception

  • 예외를 잡아서(catch) 해당 예외에 대한 어떤 처리를 할 필요가 없는 예외이다.
  • 명시적으로 잡아서 어떤 처리를 할 필요는 없다.
  • 대표적인 예 : NullPointerException, ArrayIndexOutOfBoundsException

 

 

코드를 잘못 작성해서 생기는 이런 오류들은 모두 RuntimeException을 상속한 예외들이다.

Java나 Spring에서 수많은 RuntimeException을 지원하지만 RuntimeException을 이용해서

직접 예외(Exception)을 만들어야 하는 경우도 있다.

 

 

 

➤ 의도적으로 예외를 던질 수 (throw) 있는 상황

 

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

 

의도적으로 예외를 던져서 클라이언트 쪽에 발생한 에러 정보를 알려줄 수 있다.

 

 

 

➤ 의도적인 예외 throw / catch

 

java에서 throw 키워드는 예외를 메서드 바깥으로 던져준다.

던져진 예외는 메서드 바깥 즉, 메서드를 호출한 지점으로 던져지게 된다.

 

그렇다면 서비스 계층에서 예외를 던지면 어디로 던져질까?

서비스 계층의 메서드는 API 계층인 Controller의 Handler Method가 이용한다.

-> 서비스 계층에서 던져진 예외는 Controller의 Handler Method 쪽에서 잡아서 처리할 수 있다.

 

따라서 Controller에서 발생하는 예외 처리를 위한 Exception Advice 클래스에 서비스 계층에서 던진 예외도 작성해서 처리하면 된다.

 

 

✅ 서비스 계층에서 의도적으로 던질 수 있는 예외는 1가지만 존재하진 않는다.

예를 들어 아래의 경우 모두 예외 상황이 생길 수 있다.

  • 회원 정보가 존재하지 않을 경우
  • 회원 등록 시 이미 존재하는 회원일 경우
  • 로그인 패스워드 검증에서 일치하지 않는 경우

 

이럴 때 handle에러이름Exception()과 같이 메서드 이름을 짓는 것은 적절하지 않다.

그리고 추상적인 RuntimeException 을 그대로 전달 받는 것도 바람직 하진 않다.

 

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

 

 

 

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

 

ExceptionCode 를 enum으로 미리 직접 작성해둔다.

BusinessLogicException 클래스를 생성하여 RuntimeException을 상속한다.

super() 상위 클래스 생성자로 RuntimeException에 구체적인 예외 정보 (Exception Code)를 던진다.

 

Exception Advice 클래스에서

메서드 명이 서비스 계층의 비즈니스 로직 처리에서 발생하는 예외를 처리하는 것을 목적으로 한다.

-> handleBusinessLogicException

 

RuntimeException을 파라미터로 전달 받던 것을 BusinessLogicException을 전달 받는 것으로 변경한다.

 

@ResponseStatus(HttpStatus.NOT_FOUND) 제거

ResponseStatus는 고정된 HttpStatus를 지정하므로 다양한 Status를 동적으로 처리할 수 없다.

-> 대신 ResponseEntity를 사용해서 커스텀한 예외 코드(상태)를 전달한다.

 

 

 

▶️ @RestControllerAdvice 에서 ResponseStatus vs ResponseEntity

 

@ResponseStatus : 

한가지 유형으로 고정된 예외를 처리할 경우 HttpStatus를 지정해서 사용

 

@ResponseEntity :

BusinessLogicException 처럼 다양한 유형의 Custom Exception을 처리하고자 할 경우

 

 

▶️ of() 정적 팩토리 메서드

 

new를 직접 사용하지 않고 클래스의 인스턴스를 생성한다.

장점 : 이름을 통해 객체의 의미를 알 수 있다.

 

 

 

 

 

읽어주셔서 감사합니다^^ 좋은하루 되세요 🤗

 

 

 

 

 

 

Spring MVC에서의 예외 처리

 

 

Spring MVC는 애플리케이션에서 발생할 수 예외를 효율적으로 처리하도록 몇 가지 방법을 제공한다.

 

 

 

➤ @ExceptionHandler를 이용한 Controller 레벨에서의 예외 처리

 

유효성 검증에서 실패했을 때 클라이언트가 전달 받는 Response Body는 애플리케이션에서 예외(Exception)가 발생했을 때, 내부적으로 Spring에서 전송해주는 에러 응답 메시지 중 하나이다.

 

Spring에서 예외는 유효성 검증이 실패했을 때 등 문제가 발생하면 실패를 하나의 예외로 간주하여 이 예외를 던져서 (throw) 예외 처리를 유도한다.

 

 

▶️ @Slf4J

Simple Logging Facade for Java

Logging

  • 개발 중이나 완료 후 발생할 수 있는 오류에 대해 디버깅하거나 운영중인 프로그램 상태를 모니터링 하기 위해 필요한 정보(로그)를 기록하는것

 

Logback과 같은 백엔드 Logging Framework의 facade pattern

 

https://www.slf4j.org/

 

SLF4J

Simple Logging Facade for Java (SLF4J) The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framewor

www.slf4j.org

 

 

▶️ @ExceptionHandler

예외 처리 메서드로 선언

 

 

▶️ .getBindingResult().getFieldErrors()

에러 정보를 확인할 수 있다

 

 

➤ ErrorResponse 클래스 만들기

 

DTO 클래스 유효성 검증 실패 시

실패한 필드에 대한 Error 정보만 담아서 응답으로 전송하기 위한 클래스

 

Response Body의 JSON 응답 객체가 배열이다.

✅ 배열인 이유?

DTO 클래스에서 유효성 검증에 실패하는 멤버변수가 하나 이상이 될 수 있기 때문에 유효성 검증 실패 에러 역시 하나 이상이 될 수 있다.

-> 유효성 검증에 실패한 필드의 에러 정보를 담기 위해서 List 객체를 이용

FieldError라는 별도의 static class를 ErrorResponse 클래스의 안에 정의한다.

FieldError클래스는 ErrorResponse 클래스의 내부(Inner) 클래스라고 부르기 보다는

ErrorResponse 클래스의 static 멤버 클래스라고 부르는 것이 적절하다.

필요한 정보들만 골라서 Response body에 담을 수 있다.

 

 

 

➤ @ExceptionHandler 의 단점

 

1. 코드 중복이 발생한다.

각각의 Controller 클래스에서 @ExceptionHandler 애너테이션을 사용해서 유효성 검증 실패에 대한 에러 처리를 해야한다.

 

2. 예외 마다 에러 처리 핸들러 메서드가 늘어난다.

Controller 클래스 내에서 발생할 수 있는 예외(Exception)은 한가지만 있는게 아니다.

그러면 각각의 예외 마다 @ExceptionHandler를 추가한 에러 처리 핸들러 메서드가 늘어나게 된다.

 

 

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more com

docs.spring.io

 

 

 

 

❑ @RestControllerAdvice 를 이용한 예외 처리

 

➤ 예외 처리 공통화

 

특정 클래스에 @RestControllerAdvice 애너테이션을 추가하면 여러개의 Controller 클래스에서 @ExceptionHandler, @InitBinder, @ModelAttribute가 추가된 메서드를 공유해서 사용할 수 있다.

 

 

@RestControllerAdvice 애너테이션을 추가한 클래스

➡️ 예외 처리를 모든 Controller에 공통화 할 수 있게 된다.

 

 

▶️ @InitBinder, @ModelAttribute 애너테이션

 

JSP, Thymeleaf 같은 서버 사이드 렌더링(SSR, Server Side Rendering) 방식에서 주로 사용된다.

 

 

 

Controller 클래스에서 @ExceptionHandler 로직 제거

-> ExceptionAdvice 클래스 정의 (@RestControllerAdvice)

-> 모든 Controller의 에러를 동시에 처리할 수 있게 된다.(AOP, 공통 관심사)

 

에러를 정보를 전달하는 객체(Error Response)와 에러 정보를 추출하고 가공하는 static 멤버 클래스로 역할을 분리한다.

 

기능이 늘어남에 따라 Error Response 클래스의 구현 복잡도가 늘어나지만

에러 유형에 따른 에러 정보 생성 역할을 분리함으로써 사용자 입장에서 한층 더 편리해진다.

 

 

▶️ of() 메서드

  • 네이밍 컨벤션(Naming Convention)
  • 객체 생성시 어떤 값들의(of~) 객체를 생성한다는 의미
  • 객체.of(원하는 값)

 

ExceptionAdvice 클래스에서 ErrorResponse 클래스에 of를 사용해서 에러 객체 전달한다.

ResponseEntity 대신 ErrorResponse 객체 그대로 반환하고

@ResponseStatus 애너테이션을 이용해서 attribute로 HTTP Status 전달한다.

 

 

※ @RestControllerAdvice vs @ControllerAdvice

 

SpringMVC 4.3버전 이후부터 @RestControllerAdvice 지원

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

JSON 형식의 데이터를 Response Body로 전송하기 위해서 ResponseEntity로 데이터를 래핑할 필요가 없다.

 

 

 

 

읽어주셔서 감사합니다. 좋은하루 되세요 🤩

 

1