Custom Exception

GEONNY·2024년 8월 5일
0

Building-API

목록 보기
17/28
post-thumbnail

개발을 하면서 발생할 수 있는 Exception 을 모두 처리하는건 쉬운 일이 아닙니다. GlobalExceptionHandler 를 쓴다 해도 Exception 이 발생할 때 마다 @ExceptionHandler를 추가하면 그 양도 많아져 관리가 힘들게 됩니다. 하나의 Exception 으로 여러 Exception 관련 처리를 하면서 원하는 메시지와 코드를 클라이언트에게 전달할 수 있도록 Custom Exception 을 생성해 활용하는 방법을 알아보겠습니다.

📌extends RuntimeException

명칭은 ServiceException 으로 하고 RuntimeException 을 상속 받는 class 를 생성합니다.
common.exception.custom.ServiceException

public class ServiceException extends RuntimeException {
}

📌method overload

이전에 작성했던 Enum type의 ErrorCode 를 클래스 변수로 선언하고 접근할 수 있는 getter method 도 추가 합니다. 그리고 message 나 Throwable 을 선택적으로 받을 수 있도록 생성자를 overload 합니다.

public class ServiceException extends RuntimeException {
    private final ErrorCode errorCode;

    public ServiceException(ErrorCode errorCode) {
        super(errorCode.messageCode());
        this.errorCode = errorCode;
    }

    public ServiceException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public ServiceException(ErrorCode errorCode, Throwable throwable) {
        super(throwable.getMessage(), throwable);
        this.errorCode = errorCode;
    }
    
    public ErrorCode errorCode() {
        return this.errorCode;
    }
}

📌GlobalExceptionHandler 에 추가

이제 ServiceException 에 대한 공통 처리를 위해 GlobalExceptionHandler 에 추가합니다.
common.exception.GlobalExceptionHandler

@ExceptionHandler(value = {ServiceException.class})
public ResponseEntity<ErrorResponse> handleServiceException(ServiceException e) 
    return generateErrorResponse(e.errorCode(), e);
}

새로 발생하는 Exception 을 처리해야 한다면 ErrorCode에 추가하여 ServiceException 내부 코드로 전달하면 하나의 ServiceException 으로 여러 Exception 관련 처리를 할 수 있습니다.
테스트를 위해 createMember method 에 memberName 은 한글/영문만 가능한 조건문을 추가 합니다. 그리고 조건에 해당하는 경우 ServiceException 을 throw 합니다.

@Override
public MemberCreateResponse createMember(MemberCreateRequest parameter) {
    if (memberRepository.existsById(parameter.memberId())) {
        throw new EntityExistsException("이미 존재하는 ID 입니다. -> " 
        	+ parameter.memberId());
    }
    if (StringUtils.isEmpty(parameter.memberName())
            || !Pattern.compile("^[가-힣a-zA-Z]+$").matcher(
            		parameter.memberName()).matches()) {
        throw new ServiceException(
        	ErrorCode.INVALID_PARAMETER, "이름은 한글/영문만 가능합니다.");
    }
    Member newMember = Member.builder()
            .memberId(parameter.memberId())
            .password(parameter.password())
            .memberName(parameter.memberName())
            .useYn(parameter.useYn())
            .build();
    memberRepository.save(newMember);
    return MemberCreateResponse.builder()
            .memberId(newMember.getMemberId())
            .memberName(newMember.getMemberName())
            .useYn(newMember.getUseYn())
            .createDate(newMember.getCreateDate())
            .updateDate(newMember.getUpdateDate())
            .build();
}

한글 이외의 데이터를 전달하여 throw 한 ServiceException 의 정보가 출력되는지 확인합니다.

console

com.geonlee.api.common.exception.custom.ServiceException: 이름은 한글/영문만 가능합니다.
이하 생략...

응답 구조체와 로그가 구현한데로 출력되었습니다.
다국어 처리를 해야하는 경우 ErrorCode 와 message.properties에 추가하고 ErrorCode 만 전달하게 수정하면 됩니다.

📚참고

📕Checked Unchecked Exception

checked Exception 은 Exception 을 상속받으면서 Runtime Exception 하위 클래스가 아닌 Exception 을 말합니다. 예외 처리(try ~ catch or throws) 를 하지 않으면 컴파일 오류가 발생하여 반드시 예외처리를 해줘야 합니다.
보통 파일이나 네트워크와 같이 외부환경 요인과 관련된 예외로 IOException, SQLException 등 이 있습니다.

Unchecked Exception 은 RuntimeException 을 상속받은 클래스를 말합니다. 말 그대로 runtime 중에 발생할 수 있는 예외로, 예외처리를 강제하진 않습니다.
프로그래밍에서 발생하는 논리적 오류와 관련된 예외로 NullPointerException, IllegalArgumentException 등이 있습니다.

ServiceException 에서 RuntimeException 을 상속받은 이유는 예외 처리를 강제화 하지 않아 코드의 간결성과 가독성을 높이기 위함입니다.

profile
Back-end developer

0개의 댓글