Spring 프로젝트 시작하기 - 예외처리편

API는 동작중 여러 오류사항을 마주할 수 있다.

오류가 발생했을때 클라이언트쪽에서 에러를 판별하고 적절한 처리를 할 수 있도록 예외를 리턴해주어야한다.

 

우선 boot-starter-web가 없다면 의존성을 추가해준다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
}

 

1. Response 규정

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ServerResponse {
    SUCCESS(HttpStatus.OK, "OK", HttpStatus.OK.getReasonPhrase())
    , INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "ER40100", "Invalid token")
    , EXPIRE_TOKEN(HttpStatus.UNAUTHORIZED, "ER40101", "Token expired")
    , FORBIDDEN(HttpStatus.FORBIDDEN, "ER40300", HttpStatus.FORBIDDEN.getReasonPhrase())
    , INVALID_REQUEST(HttpStatus.BAD_REQUEST, "ER40000", "유효하지 않은 요청입니다")
    , INVALID_ACCOUNT(HttpStatus.BAD_REQUEST, "ER40001", "Account information not available.")
    , DATA_NOT_EXIST(HttpStatus.BAD_REQUEST, "ER40002", "Data does not exist")
    , VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "ER40003", "Request body has an invalid value format.")
    , DUPLICATE_NAME_ERROR(HttpStatus.BAD_REQUEST, "ER40011", "Duplicate name")
    , INVALID_LOGIN(HttpStatus.BAD_REQUEST, "ER40012", "The ID or password does not match.")
    , SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "ER50000", "Contact your administrator")
    ;
    public final int status;
    public final String code;
    public final String message;

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

개발중에 리턴해줄 예외 Response를 규정한다.

첫번째 인자로 HTTP status값

두번째 인자로 프로젝트내에서 처리할 코드

세번째 인자로 에러 메세지를 넣었다.

 

2. Exception Handler 생성

@RestControllerAdvice
@Slf4j
public class ServerExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
            @NonNull HttpRequestMethodNotSupportedException ex,
            @NonNull HttpHeaders headers,
            @NonNull HttpStatusCode status,
            @NonNull WebRequest request) {
        log.error("handleHttpRequestMethodNotSupported() : 유효하지 않은 메소드 진입", ex);
        ServerResponse serverResponse = ServerResponse.INVALID_METHOD;
        return ResponseEntity.status(serverResponse.status).body(serverResponse);
    }

    ... 잠시생략 ...
}

첫번째로 handleHttpRequestMethodNotSupported 핸들러를 넣었다.

URL은 맞지만 Method가 맞지 않을때 이 메소드를 타게된다.

 

@RestControllerAdvice
@Slf4j
public class ServerExceptionHandler extends ResponseEntityExceptionHandler {

    ... 잠시생략 ...

    /**
     * 유효성 검사 실패는 400
     *
     * @param ex MethodArgumentNotValidException
     * @return 400 올바르지 않은 형식의 입력값입니다
     */
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            @NonNull MethodArgumentNotValidException ex,
            @NonNull HttpHeaders headers,
            @NonNull HttpStatusCode status,
            @NonNull WebRequest request) {
        log.error("handleMethodArgumentNotValid(): 유효성 검사 실패", ex);
        ex.printStackTrace();
        ServerResponse serverResponse = ServerResponse.VALIDATION_ERROR;
        return ResponseEntity.status(serverResponse.status).body(serverResponse);
    }

    ... 잠시생략 ...
}

 

handleMethodArgumentNotValid는 Request Body로 받은 값이 유효하지 않을때 타게된다.

 

@RestControllerAdvice
@Slf4j
public class ServerExceptionHandler extends ResponseEntityExceptionHandler {

    ... 잠시생략 ...

    /**
     * 잘못된 요청은 400
     *
     * @param exception BadRequestException
     * @return 400 유효하지 않은 요청입니다
     */
    @ExceptionHandler(value = {BadRequestException.class})
    public ResponseEntity<ServerResponse> badRequestExceptionHandler(BadRequestException exception) {
        log.error("badRequestExceptionHandler() : 잘못된 요청", exception);
        exception.printStackTrace();
        ServerResponse serverResponse = exception.getServerResponse();
        return ResponseEntity.status(serverResponse.status).body(serverResponse);
    }

    ... 잠시생략 ...
}

badRequestExceptionHandler는 API를 요청할때 요청값이 올바르지 않을때 리턴한다.

ResponseEntityExceptionHandler에서 명시된 메소드는 아니며, 서비스로직에서 리턴시킬 용도로 만들었다.

아래 Exception과 같이 쓰여야한다.

 

@Getter
public class BadRequestException extends RuntimeException {
	ServerResponse serverResponse;
	public BadRequestException(ServerResponse serverResponse) {
		super(serverResponse.message);
		this.serverResponse = serverResponse;
	}
}

 

 

@RestControllerAdvice
@Slf4j
public class ServerExceptionHandler extends ResponseEntityExceptionHandler {

    ... 잠시생략 ...

    /**
     * BadRequestException 외 모든 Exception은 500
     *
     * @param exception exception
     * @return 500 관리자에게 문의하세요
     */
    @ExceptionHandler(value = {ServerException.class, Exception.class})
    public ResponseEntity<ServerResponse> serverExceptionHandler(Exception exception) {
        log.error("serverExceptionHandler() : 서버 오류 진입", exception);
        exception.printStackTrace();
        ServerResponse serverResponse = ServerResponse.SERVER_ERROR;
        return ResponseEntity.status(serverResponse.status).body(serverResponse);
    }
}

나머지 예외처리를 못한 오류에대해서는 ServerException 오류를 리턴한다.

3. 테스트

동작중인 서비스에서 강제로 BadRequestException을 보내보았다.

 

리턴값이 ServerResponse의 형태대로 출력되었다.


Method를 바꾼 경우에도 오류값이 예상대로 출력되었다