본문 바로가기

Spring

[Spring Boot] Rest API 만들기(4) 예외 처리 @RestControllerAdvice

Rest Api 구현중 오류 메시는 개발자가 의도한 오류(Custom Exception)와 예상치 못한 System Exception으로 구분됩니다. 이번 포스팅에서는 이러한 예외상황에 대해 @RestControllerAdvice를 활용하여 공통 예외 처리(Exception handler) 적용하는 방법에 대해 알아보겠습니다.

 

<네이버 오픈API 오류 메시지 형식>

네이버 오픈 API 오류 메시지 형식 가이드처럼 API 오류 메시지에 대해 일관된 형식으로 응답하도록 설계해야 합니다.

신규 API를 구현할 때마다 작성하도록 설계하는 경우 작업자마다 일관된 응답 구조를 보장하기 어렵기 때문에 별도의 작업을 하지 않아도 일관된 오류 메시지 형식으로 응답할 수 있도록 해야 합니다.

 

 

 

 

 

1. 에러 응답 모델 생성

일관된 오류 메시지 형식으로 응답하기 위해 공통 ExceptionEntity를 생성합니다.

 

package kr.co.sample.sampleapi.common.response;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.springframework.http.HttpStatus;

@Getter
@ToString
public class ApiExceptionEntity {
    private String errorCode;
    private String errorMessage;

    @Builder
    public ApiExceptionEntity(HttpStatus status, String errorCode, String errorMessage){
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }
}

 

 

 

 

 

2. 에러 Enum Class 선언

Enum Class를 활용하여 Custom 한 오류 메시지를 선언합니다.

 

package kr.co.sample.sampleapi.common.enums;

import lombok.Getter;
import lombok.ToString;
import org.springframework.http.HttpStatus;

@Getter
@ToString
public enum ExceptionEnum {
    RUNTIME_EXCEPTION(HttpStatus.BAD_REQUEST, "E0001"),
    ACCESS_DENIED_EXCEPTION(HttpStatus.UNAUTHORIZED, "E0002"),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "E0003"),

    SECURITY_01(HttpStatus.UNAUTHORIZED, "S0001", "권한이 없습니다."),   

    private final HttpStatus status;
    private final String code;
    private String message;

    ExceptionEnum(HttpStatus status, String code) {
        this.status = status;
        this.code = code;
    }

    ExceptionEnum(HttpStatus status, String code, String message) {
        this.status = status;
        this.code = code;
        this.message = message;
    }
}

 

 

 

 

 

3. Custom 에러 구현

개발자가 의도한 예외상황에 사용하기 위해 공통 Exception을 구현합니다.

 

package kr.co.sample.sampleapi.common.response;

import kr.co.sample.sampleapi.common.enums.ExceptionEnum;
import lombok.Getter;

@Getter
public class ApiException extends RuntimeException {
    private ExceptionEnum error;

    public ApiException(ExceptionEnum e) {
        super(e.getMessage());
        this.error = e;
    }
}

 

그러면 아래와 같이 예외처리를 할 수 있습니다.

 

throw new ApiException(ExceptionEnum.SECURITY_01);

 

 

 

 

 

3. Exception handler 구현

이제 @RestControllerAdvice를 활용하여 공통된 오류 메시지 형식으로 응답하도록 구현합니다.

 

package kr.co.sample.sampleapi.common.response;

import kr.co.sample.sampleapi.common.enums.ExceptionEnum;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.nio.file.AccessDeniedException;

@RestControllerAdvice
public class ApiExceptionAdvice {
    @ExceptionHandler({ApiException.class})
    public ResponseEntity<ApiExceptionEntity> exceptionHandler(HttpServletRequest request, final ApiException e) {
        //e.printStackTrace();
        return ResponseEntity
                .status(e.getError().getStatus())
                .body(ApiExceptionEntity.builder()
                        .errorCode(e.getError().getCode())
                        .errorMessage(e.getError().getMessage())
                        .build());
    }

    @ExceptionHandler({RuntimeException.class})
    public ResponseEntity<ApiExceptionEntity> exceptionHandler(HttpServletRequest request, final RuntimeException e) {
        e.printStackTrace();
        return ResponseEntity
                .status(ExceptionEnum.RUNTIME_EXCEPTION.getStatus())
                .body(ApiExceptionEntity.builder()
                        .errorCode(ExceptionEnum.RUNTIME_EXCEPTION.getCode())
                        .errorMessage(e.getMessage())
                        .build());
    }

    @ExceptionHandler({AccessDeniedException.class})
    public ResponseEntity<ApiExceptionEntity> exceptionHandler(HttpServletRequest request, final AccessDeniedException e) {
        e.printStackTrace();
        return ResponseEntity
                .status(ExceptionEnum.ACCESS_DENIED_EXCEPTION.getStatus())
                .body(ApiExceptionEntity.builder()
                        .errorCode(ExceptionEnum.ACCESS_DENIED_EXCEPTION.getCode())
                        .errorMessage(e.getMessage())
                        .build());
    }

    @ExceptionHandler({Exception.class})
    public ResponseEntity<ApiExceptionEntity> exceptionHandler(HttpServletRequest request, final Exception e) {
        e.printStackTrace();
        return ResponseEntity
                .status(ExceptionEnum.INTERNAL_SERVER_ERROR.getStatus())
                .body(ApiExceptionEntity.builder()
                        .errorCode(ExceptionEnum.INTERNAL_SERVER_ERROR.getCode())
                        .errorMessage(e.getMessage())
                        .build());
    }
}

 

 

 

 

 

4. 결과

Enum Class에 선언된 HTTP상태 코드(HttpStatus)ApiExceptionEntity 형식에 맞게 응답이 됩니다.

 

 

@RestControllerAdvice 구현 중 ResponseEntity를 활용하여 http 상태(status) 값도 200이 아닌 값으로 설정할 수 있습니다. 예외 응답 메시지 설계에 자유롭게 활용 가능합니다.