레벨테스트 controller와 restController 두가지로 나눠서 개발을 목표로 하고 있다.
개발에 앞서 에러 핸들링을 하기 위해서 구글링을 해봤다.
우선 첫번째로
package com.study.exception;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(final RuntimeException e) {
log.error("handleRuntimeException : {}", e.getMessage());
return e.getMessage();
}
}
@RestControllerAdvice
스프링은 예외 처리를 위해 @ControllerAdvice
와 @ExceptionHandler
등의 기능을 지원해 준다고 한다.
@ControllerAdvice
는 컨트롤러 전역에서 발생할 수 있는 예외를 잡아 Throw 해주고,
@ExceptionHandler
는 특정 클래스에서 발생할 수 있는 예외를 잡아 Throw 한다.
일반적으로 @ExceptionHandler
는 @ControllerAdvice
가 선언된 클래스에 포함된 메서드에 선언한다.
@Slf4j
롬복에서 제공해주는 기능으로, 해당 어노테이션이 선언된 클래스에 자동으로 로그 객체를 생성한다.
코드에서 보시는 것처럼 log.error( )
, log.debug( )
와 같이 로깅 관련 메서드를 사용할 수 있다.
@ExceptionHandler(RuntimeException.class)
restController에서 아래와 같이 구현한다면
@GetMapping("/test")
public String test() {
throw new RuntimeException("Holy! Exception...");
}
@ExceptionHandler
에 지정된 예외와 동일한 예외, 즉 RuntimeException
이 발생하면
GlobalExceptionHandler
는 handleRuntimeException( )
메서드를 실행한다.
모든 예외를 하나의 Enum 클래스로 관리한다.
package com.study.exception;
import org.springframework.http.HttpStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ErrorCode {
/*
* 400 BAD_REQUEST: 잘못된 요청
*/
BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."),
/*
* 404 NOT_FOUND: 리소스를 찾을 수 없음
*/
POSTS_NOT_FOUND(HttpStatus.NOT_FOUND, "게시글 정보를 찾을 수 없습니다."),
/*
* 405 METHOD_NOT_ALLOWED: 허용되지 않은 Request Method 호출
*/
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 메서드입니다."),
/*
* 500 INTERNAL_SERVER_ERROR: 내부 서버 오류
*/
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "내부 서버 오류입니다."),
;
private final HttpStatus status;
private final String message;
}
전체적인 예외를 관리할 Eunm 클래스가 필요하다.
status
HTTP 상태 코드를 상수로 선언해둔 HttpStatus 타입의 멤버로,
예외에 대한 상태 코드(status)와 이름(error)을 처리하는 데 사용한다.
message
예외에 대한 응답 메시지(message)를 처리하는 데 사용한다.
package com.study.exception;
import java.time.LocalDateTime;
import lombok.Getter;
@Getter
public class ErrorResponse {
private final LocalDateTime timestamp = LocalDateTime.now();
private final int status;
private final String error;
private final String code;
private final String message;
public ErrorResponse(ErrorCode errorCode) {
this.status = errorCode.getStatus().value();
this.error = errorCode.getStatus().name();
this.code = errorCode.name();
this.message = errorCode.getMessage();
}
}
404 Error Response와 유사한 형태를 가진, 예외 응답을 처리할 Response 클래스다.
해당 클래스는 ErrorCode를 통한 객체 생성만을 허용한다.
package com.study.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
}
다음으로 개발자가 ErrorCode에 직접 정의한 Custom 예외를 처리할 Exception 클래스다.
ErrorResponse와 마찬가지로 ErrorCode를 통한 객체 생성만을 허용한다.
Unchecked Exception인 RuntimeException을 상속받아야 한다.
처음에 만들어둔 GlobalExceptionHandler을 수정한다.
package com.study.exception;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/*
* Developer Custom Exception
*/
@ExceptionHandler(CustomException.class)
protected ResponseEntity<ErrorResponse> handleCustomException(final CustomException e) {
log.error("handleCustomException: {}", e.getErrorCode());
return ResponseEntity
.status(e.getErrorCode().getStatus().value())
.body(new ErrorResponse(e.getErrorCode()));
}
/*
* HTTP 405 Exception
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(final HttpRequestMethodNotSupportedException e) {
log.error("handleHttpRequestMethodNotSupportedException: {}", e.getMessage());
return ResponseEntity
.status(ErrorCode.METHOD_NOT_ALLOWED.getStatus().value())
.body(new ErrorResponse(ErrorCode.METHOD_NOT_ALLOWED));
}
/*
* HTTP 500 Exception
*/
@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(final Exception e) {
log.error("handleException: {}", e.getMessage());
return ResponseEntity
.status(ErrorCode.INTERNAL_SERVER_ERROR.getStatus().value())
.body(new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR));
}
}
CustomException과 HTTP 405, HTTP 500에 대한 Handler가 추가되었다.
ResponseEntity<ErrorResponse>
ResponseEntity<T>
는 HTTP Request에 대한 응답 데이터를 포함하는 클래스로,
<Type>
에 해당하는 데이터와 HTTP 상태 코드를 함께 리턴할 수 있다.
우리는 예외가 발생했을 때, ErrorResponse 형식으로 예외 정보를 Response로 리턴한다.
package com.study.board.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.study.exception.CustomException;
import com.study.exception.ErrorCode;
@RestController
@RequestMapping("/api")
public class BoardApiController {
@GetMapping("/test")
public String test() {
throw new CustomException(ErrorCode.POSTS_NOT_FOUND);
}
}
정상적인 예외 처리는 비즈니스 로직을 담당하는 Service Layer에서 이루어져야 한다.
출처