개발일지 - 에러 핸들링

아침7시개발·2022년 1월 12일
0

개발일지

목록 보기
3/19

레벨테스트 controller와 restController 두가지로 나눠서 개발을 목표로 하고 있다.
개발에 앞서 에러 핸들링을 하기 위해서 구글링을 해봤다.

우선 첫번째로

전역 예외 핸들링

GlobalExceptionHandler 생성하기

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이 발생하면

GlobalExceptionHandlerhandleRuntimeException( ) 메서드를 실행한다.


Enum 생성하기

모든 예외를 하나의 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)를 처리하는 데 사용한다.


예외 응답을 처리할 Response 클래스 생성하기

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를 통한 객체 생성만을 허용한다.


Custom 예외 처리용 Exception 클래스 생성하기

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 수정하기

처음에 만들어둔 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로 리턴한다.

RestController 수정하기

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에서 이루어져야 한다.


출처

됴뎡이

profile
쉬엄쉬엄하는 개발자

0개의 댓글