[ExceptionHandler] 예외 처리를 한 곳에서
[ExceptionHandler] 예외 처리를 한 곳에서

[ExceptionHandler] 예외 처리를 한 곳에서

Tags
Spring
Core
ExceptionHandler
Published
November 20, 2023
Author
lkdcode
예외 발생에 대한 처리는 try~catch 구문으로 컨트롤한다.가독성을 해치고, 관리에 용이하지 않다.한 곳에서 혹은 한 모듈안에서 해결해보자.
 
💡 게시글 도메인 중
게시글 아이디(pk)로 찾을 수 없거나,
게시글 수정/삭제에 대한 권한이 없는 경우 예외를 발생시키고
클라이언트에게 메시지를 전달해보자
 
위의 시나리오대로 CustomException 을 만들고
ExceptionHandler 를 통해 예외를 핸들링해보자.
 
🎯 CustomException
만약, 게시글을 찾을 수 없거나 수정/삭제에 대한 권한이 없는데 접근한 경우
발생시킬 Post~Exception 을 만든다.
현재 스터디에서는 Exception 과 ErrorCode 를 전역적으로 사용하기 때문에 최상위 클래스로 두어 타입을 묶어준다.
 
🎯 Global-AppException
모든 커스텀 익셉션은 AppException 을 상속한다.
AppErrorCode 를 매개변수로 받아 메시지를 전달한다.
public class AppException extends RuntimeException { @Getter private final AppErrorCode errorCode; public AppException(AppErrorCode errorCode) { super(errorCode.getMessage()); this.errorCode = errorCode; } }
 
🎯 Global-AppErrorCode
마찬가지로 전역적으로 사용할 에러 코드의 인터페이스다.
메시지와 Http 상태 코드의 게터를 구현해야한다.
public interface AppErrorCode { String getMessage(); HttpStatus getHttpStatus(); }
 
🎯 게시글 아이디(pk)로 찾을 수 없을 때 발생 시킬 Exception
게시글의 아이디(pk)로 조회했을 때 찾을 수 없다면 발생시킬 Exception 이다.
public class PostNotFoundByIdException extends AppException { @Getter private final Long id; public PostNotFoundByIdException(AppErrorCode errorCode, Long id) { super(errorCode); this.id = id; } }
 
🎯 게시글 ErrorCode
AppErrorCode 인터페이스의 구현체로 만든다.
enum 은 열거형 상수들의 모음이다.
@RequiredArgsConstructor@Getter 어노테이션으로
간단하게 구현이 가능하다.
해당 ErrorCode 가 어떤 ErrorCode 인지 네이밍을 통해 의도를 드러내자.
@RequiredArgsConstructor @Getter public enum PostErrorCode implements AppErrorCode { NOT_FOUND_POST_BY_ID_ERROR("존재하지 않는 게시글입니다. Id : ", HttpStatus.BAD_REQUEST), UNAUTHORIZED_POST_UPDATE_ERROR("본인 글만 업데이트할 수 있습니다.", HttpStatus.BAD_REQUEST), UNAUTHORIZED_POST_DELETE_ERROR("본인 글만 삭제할 수 있습니다.", HttpStatus.BAD_REQUEST), ; private final String message; private final HttpStatus httpStatus; }
 
🎯 Service
비즈니스 로직을 처리하는 부분에서의 코드다.
예외를 던지는 메서드를 내부 메서드로 따로 분리를 했다.
@Service @RequiredArgsConstructor public class PostCommandService implements PostCommandUsecase { /* * ... */ private User loadUserFrom(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundByIdException(UserErrorCode.NOT_FOUND_USER_BY_ID_ERROR)); } private Post loadPostFrom(Long id) { return postRepository.findById(id) .orElseThrow(() -> new PostNotFoundByIdException(PostErrorCode.NOT_FOUND_POST_BY_ID_ERROR, id)); } }
loadUserFrom : 유저의 ID 로 유저를 찾는 메서드다. 존재하지 않을 경우 예외를 던진다.
loadPostFrom : 게시글의 ID로 게시글을 찾는 메서드다. 존재하지 않을 경우 예외를 던진다.
(User~Exception 도 같은 방법으로 만들었다.)
 
🎯 ExceptionHandler
예외를 던지는 곳은 Service 에서 비즈니스 로직을 처리할 때다.
캐치를 하는 곳은 Controller 가 아닌 ExceptionHandler 이다.
캐치하는 코드를 만들자.
@RestControllerAdvice @Slf4j public class PostExceptionHandler { @ExceptionHandler(PostNotFoundByIdException.class) public ResponseEntity<?> catchPostNotFoundByIdException(PostNotFoundByIdException e) { String errorMessage = e.getMessage() + e.getId(); int httpStatus = e.getErrorCode().getHttpStatus().value(); log.error("catchPostNotFoundByIdException : " + errorMessage); return ResponseEntity .status(httpStatus) .body(errorMessage); } }
@RestControllerAdvice : Spring MVC 에서 예외 처리를 전역적으로 관리하기 위한 어노테이션이다.
@Slf4j : 에러 로깅을 위한 어노테이션이다.
@ExceptionHandler : 특정 예외 타입에 대한 처리 메서드를 정의한다.
@ExceptionHandler(xxx.class) : xxx.class 익셉션이 발생할 경우 캐치를 하겠다는 뜻이다. 여러 개의 class를 명시할 수 있다.
해당 클래스 익셉션 발생시 캐치 후 클라이언트에게 상태 코드와 메시지를 전달한다.