Custom Exception과 Exception Handler가 필요한 이유
Custom Exception은 어플리케이션에서 발생하는 다양한 에러를 의미있게 정의하기 위해 사용한다.
표준 exception class, 예를 들어 IllegalArgumentException, NullPointerException 같이 일반적인 에러를 표현해주는 exception들이 이미 존재하지만, 우리가 개발하는 어플리케이션의 비즈니스 로직에 맞는 구체적인 정보를 전달하기에는 부족하다.
따라서 Custom Exception을 통해 명확하고 구체적인 에러 메시지와 데이터를 제공하는 것이 바람직하다.
Exception handler는 exception 발생에 따른 후처리를 어떻게 할 것인지에 대한 구현체이다.
동일한 유형의 exception에 대하여 예외 처리를 중앙화함으로써 코드 중복을 줄이면서 유지보수를 쉽게 한다.
다양한 exception 상황에 대하여 HHTP 상태 코드와 메시지를 클라이언트단에 반환하여 일관된 응답 구조를 보장한다.
Custom Exception 정의
Custom Exception은 표준 exception 클래스를 상속받아 정의한다.
나는 프로젝트 하위에 exception 패키지를 생성하고 주요 비즈니스 도메인과 관련된 exception들을 categorization 한다.
예를들어 유저나 클랜같이 커뮤니티 관련 exception은 CommunityException으로 관리하고,
주식 매매에 관련된 exception은 TradingException으로 분류하여 관리하는 편이다.
@Getter
public abstract class AbstractException extends RuntimeException{
private final String errorCode;
protected AbstractException(String defaultMessage, String errorCode) {
super(defaultMessage);
this.errorCode = errorCode;
}
}
@Getter
public class CommunityException extends AbstractException {
private static final String DEFAULT_MESSAGE = "커뮤니티 서비스에 오류 발생.";
private static final String ERROR_CODE = "COMMUNITY_ERROR";
public CommunityException() {
super(DEFAULT_MESSAGE, ERROR_CODE);
}
public CommunityException(String customMessage, String errorCode) {
super(customMessage, errorCode);
}
}
@Getter
public class TradingException extends AbstractException {
private static final String DEFAULT_MESSAGE = "트레이딩 관련 서비스에 오류 발생.";
private static final String ERROR_CODE = "TRADING_ERROR";
public TradingException() {
super(DEFAULT_MESSAGE, ERROR_CODE);
}
public TradingException(String customMessage) {
super(customMessage, ERROR_CODE);
}
}
먼저 RuntimeException을 상속받아 비검사 예외로 구현한다.
가장 큰 범위의 abstract class로 AbstractException을 정의하고, 각 비즈니스 도메인의 대표적인 exception인
CommuntiyException과 TradingException을 정의하였다.
Custom exception을 정의할 때는 각 exception에 관련된 에러코드와 메시지를 정의하여 디버깅과 로그 분석에 도움을 줄 수 있도록 한다.
Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = {CommunityException.class})
public ResponseEntity<ExceptionResponse> handleCommunityException(RuntimeException ex, WebRequest request) {
CommunityException communityException = (CommunityException) ex;
ExceptionResponse response = ExceptionResponse.builder()
.type(communityException.getClass().getCanonicalName())
.errorCode(communityException.getErrorCode())
.message(communityException.getMessage())
.build();
return ResponseEntity.badRequest().body(response);
}
@ExceptionHandler(value = {TradingException.class})
public ResponseEntity<ExceptionResponse> handleTradingException(RuntimeException ex, WebRequest request) {
TradingException tradingException = (TradingException) ex;
ExceptionResponse response = ExceptionResponse.builder()
.type(tradingException.getClass().getCanonicalName())
.errorCode(tradingException.getErrorCode())
.message(tradingException.getMessage())
.build();
return ResponseEntity.badRequest().body(response);
}
@ExceptionHandler(value = {Exception.class})
public ResponseEntity<ExceptionResponse> handleGeneralException(Exception ex, WebRequest request) {
ExceptionResponse response = ExceptionResponse.builder()
.type(ex.getClass().getCanonicalName())
.errorCode("INTERNAL_SERVER_ERROR")
.message("서버 내부에서 알 수 없는 오류가 발생했습니다.")
.build();
return ResponseEntity.internalServerError().body(response);
}
}
Spring에서는 @RestControllerAdvice를 사용하여 RESTful 서비스 전역적으로 exception hadnling을 할 수 있다.
@ControllerAdvice는 ModelAndView 객체를 반환하므로 @ResponseBody가 포함되어있는 @RestControllerAdvice를 사용한다.
내부 메소드에 @ExceptionHandler(value = {<예외 클래스 목록>}) 과 같은 어노테이션을 붙여주면 목록에 해당하는 exception이 발생했을 때 처리할 로직을 정의할 수 있다.
Custom exception을 분류한 카테고리마다 별도의 메소드를 정의하고, 이외 exception을 처리한 전역적인 handler를 정의하면 된다.
또한 handler의 반환 값이 ResponseEntity임을 확인할 수 있다.
메소드의 반환 값은 Spring MVC의 HandlerMethodReturnValueHandler에 의해 클라이언트로 반환되고
이 때 ResponseEntity가 반환값이므로 이 내용과 상태 코드를 클라이언트에 매핑해준다.
아래와 같이 ExceptionResponse라는 exception 발생 시 반환할 responseBody를 정의하여 handler의 반환값으로 사용하자.
@Getter
@Setter
@Builder
public class ExceptionResponse {
private String type;
private String errorCode;
private String message;
}
'Backend' 카테고리의 다른 글
[Java Spring Boot] Spring Security + JWT - 1. Spring Security 역할 및 설정 방법 (0) | 2025.01.20 |
---|---|
[Java Spring Boot] JWT 토큰 알아보기 (0) | 2025.01.17 |
[Java Spring Boot] Spring AOP를 이용한 Logging System 구축 방법 (0) | 2025.01.13 |
[Java Spring Boot] Entity 복합 키 설정하기 - @Embeddable, @EmbeddedId (2) | 2025.01.13 |
[Java Spring Boot] Repository에서 복잡한 구조의 DTO 매핑하기.(QueryDSL, JPQL, JDBC Template) (0) | 2025.01.13 |