본문 바로가기

백엔드/개발과 설계 원칙

Spring 예외처리

반응형

@ControllerAdvice로 모든 예외를 핸들링


@ControllerAdvice는 모든 예외를 한 곳에서 처리할 수 있게, 컨트롤러에게 조언을 해주는 것이라고 생각하면 됩니다.

controller에서 @Valid라는 어노테이션을 사용하면, 검사 후에 적합하지 않다고 생각하고 예외를 이곳으로 보내주게 됩니다.

핸들링 할 수 있는 예외의 종류들은 아래와 같을 것 같습니다. @NotEmpty, @Email 등도 있긴 합니다. 

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     *  javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
     *  HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생
     *  주로 @RequestBody, @RequestPart 어노테이션에서 발생
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("handleMethodArgumentNotValidException", e);
        final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * @ModelAttribut 으로 binding error 발생시 BindException 발생한다.
     * ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
     */
    @ExceptionHandler(BindException.class)
    protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
        log.error("handleBindException", e);
        final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * enum type 일치하지 않아 binding 못할 경우 발생
     * 주로 @RequestParam enum으로 binding 못했을 경우 발생
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("handleMethodArgumentTypeMismatchException", e);
        final ErrorResponse response = ErrorResponse.of(e);
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * 지원하지 않은 HTTP method 호출 할 경우 발생
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.error("handleHttpRequestMethodNotSupportedException", e);
        final ErrorResponse response = ErrorResponse.of(ErrorCode.METHOD_NOT_ALLOWED);
        return new ResponseEntity<>(response, HttpStatus.METHOD_NOT_ALLOWED);
    }

    /**
     * Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생합
     */
    @ExceptionHandler(AccessDeniedException.class)
    protected ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException e) {
        log.error("handleAccessDeniedException", e);
        final ErrorResponse response = ErrorResponse.of(ErrorCode.HANDLE_ACCESS_DENIED);
        return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.HANDLE_ACCESS_DENIED.getStatus()));
    }

    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessException e) {
        log.error("handleEntityNotFoundException", e);
        final ErrorCode errorCode = e.getErrorCode();
        final ErrorResponse response = ErrorResponse.of(errorCode);
        return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
    }


    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("handleEntityNotFoundException", e);
        final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR);
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
  • handleMethodArgumentNotValidException
    • avax.validation.Valid or @Validated 으로 binding error 발생시 발생한다. )
    • HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생 주로 @RequestBody, @RequestPart 어노테이션에서 발생
  • handleBindException
    • @ModelAttribut 으로 binding error 발생시 BindException 발생한다.
  • MethodArgumentTypeMismatchException
    • enum type 일치하지 않아 binding 못할 경우 발생
    • 주로 @RequestParam enum으로 binding 못했을 경우 발생
  • handleHttpRequestMethodNotSupportedException :
    • 지원하지 않은 HTTP method 호출 할 경우 발생
  • handleAccessDeniedException
    • Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생합
    • Security에서 던지는 예외
  • handleException
    • 그 밖에 발생하는 모든 예외 처리, Null Point Exception, 등등
    • 개발자가 직접 핸들링해서 다른 예외로 던지지 않으면 모두 이곳으로 모인다.
  • handleBusinessException
    • 비즈니스 요규사항에 따른 Exception
    • 아래에서 자세한 설명 진행

추가적인 예외는 @ExceptionHandler 으로 추가해서 적절한 Error Response를 만들어서 핸들링 할 수 있습니다. 비즈니스 로직도 적용할 수 있겠죠.

Error Code 정의


public enum ErrorCode {

    // Common
    INVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),

    // Member
    EMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),

    ;
    private final String code;
    private final String message;
    private int status;

    ErrorCode(final int status, final String code, final String message) {
        this.status = status;
        this.message = message;
        this.code = code;
    }
}

에러 코드는 enum 타입으로 한 곳에서 관리하는 것이 후에 유지 보수에 더 효과적입니다. 또한 에러 메세지는 각 도메인별로 관리하는 것이 좋을 수 있습니다.

 

 

@Sl4j 사용법, 로그에 남기기


@Sl4j는 로깅에 대한 추상 레이어를 제공하는 인터페이스의 모음이다. 이는 나중에 로깅 라이브러리를 변경할 때 코드의 변경없이 가능하다는 것.

 

application.yml 설정 ( spring boot 2.3.1 )

logging:
  file:
    name: ${user.dir}/log/test.log  # 로깅 파일 위치이다.
    max-history: 7 # 로그 파일 삭제 주기이다. 7일 이후 로그는 삭제한다.
    max-size: 10MB  # 로그 파일 하나당 최대 파일 사이즈이다.
  level:  # 각 package 별로 로깅 레벨을 지정할 수 있다.
    com.project.study : error
    com.project.study.controller : debug

 

Maven or Gradle 에 lombok 라이브러리 추가

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

@SLF4J 사용법

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@Slf4j
public class TestController {
    @GetMapping("/")
    public String String(String str){
        try {
            str.toString();
        } catch (NullPointerException e){
            log.trace("가장 디테일한 로그");
            log.warn("경고");
            log.info("정보성 로그");
            log.debug("디버깅용 로그");
            log.error("에러",e);
        }
        return "test";
    }
}

 

 

** 참고


https://cheese10yun.github.io/spring-guide-exception/

 

Spring Guide - Exception 전략 - Yun Blog | 기술 블로그

Spring Guide - Exception 전략 - Yun Blog | 기술 블로그

cheese10yun.github.io

https://programmer93.tistory.com/64

 

Spring boot logging - @Slf4j 어노테이션 사용법 - 삽질중인 개발자

개발을 하거나 운영을 하는 상황에서 LOGGING 은 아주 중요하다. Spring boot에서는 로그를 남기는 방법이 많이 있다. 그중 가장 편하게 사용되는 @Slf4j 어노테이션을 알아보자. SLF4J 란? 로깅에 대한

programmer93.tistory.com

 

반응형

'백엔드 > 개발과 설계 원칙' 카테고리의 다른 글

MSA  (1) 2022.09.01
객체지향 설계 5원칙  (0) 2022.06.12
객체지향이란  (0) 2022.06.12
Impl 분류하는 이유  (0) 2022.04.10
디자인 패턴 - 빌더 패턴  (0) 2022.03.07