Home [Spring] 예외 처리
Post
Cancel

[Spring] 예외 처리

예외처리 필요성

1
2
3
4
5
6
{
  "timestamp":"2022-09-12T17:33:42.132+00:00",
  "status": 400,
  "error": "Bad Request",
  "path": "/v1/members"
}
  • 예외처리 없이 유효성 검사 실패 시 응답 메세지
    • 클라이언트는 위 Response Body의 내용만으로는 요청 데이터 중 어떤 항목이 유효성 검증에 실패했는지 알수없기에 에러메세지를 더 구체적으로 알 수 있도록 바꾸는 작업 필요

Spring MVC 예외처리

  • @ExceptionHandler 예외처리

    • 한 Controller 클래스 내에서 발생하는 예외 처리
    • Controller마다 동일하게 발생하는 예외 처리에 대한 중복 코드가 발생
    • 다양한 유형의 예외 처리에는 한계 존재
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    @RestController
    @RequestMapping("/v6/members")
    @Validated
    @Slf4j
    public class MemberController {
                ...
                ...
      @PostMapping
      public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
          Member member = mapper.memberPostDtoToMember(memberDto);
          Member response = memberService.createMember(member);
          return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
                  HttpStatus.CREATED);
      }
          ...
          ...
      @ExceptionHandler
      public ResponseEntity handleException(MethodArgumentNotValidException e) {
      
          // getBindingResult().getFieldErrors()를 통해 발생한 에러 정보를 확인
          final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
      
          // 위에서 얻은 정보를 ResponseEntity를 통해 Response Body로 전달
          return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
      }
    }
    

  • ErrorResponse Object

    • 따로 객체를 만들어 필드에 지정된 특정 값들로만 응답
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    
    @Getter
    public class ErrorResponse {
        private List<FieldError> fieldErrors;
        private List<ConstraintViolationError> violationErrors;
      
        private ErrorResponse(final List<FieldError> fieldErrors,
                            final List<ConstraintViolationError> violationErrors) {
          this.fieldErrors = fieldErrors;
          this.violationErrors = violationErrors;
      }
      
        public static ErrorResponse of(BindingResult bindingResult) {
          return new ErrorResponse(FieldError.of(bindingResult), null);
      }
      
        public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
          return new ErrorResponse(null, ConstraintViolationError.of(violations));
      }
      
        @Getter
        public static class FieldError {
          private String field;
          private Object rejectedValue;
          private String reason;
      
          private FieldError(String field, Object rejectedValue, String reason) {
              this.field = field;
              this.rejectedValue = rejectedValue;
              this.reason = reason;
          }
      
          public static List<FieldError> of(BindingResult bindingResult) {
              final List<org.springframework.validation.FieldError> fieldErrors =
                                                          bindingResult.getFieldErrors();
              return fieldErrors.stream()
                      .map(error -> new FieldError(
                              error.getField(),
                              error.getRejectedValue() == null ?
                                              "" : error.getRejectedValue().toString(),
                              error.getDefaultMessage()))
                      .collect(Collectors.toList());
          }
        }
      
        @Getter
        public static class ConstraintViolationError {
          private String propertyPath;
          private Object rejectedValue;
          private String reason;
      
          private ConstraintViolationError(String propertyPath, Object rejectedValue,
                                     String reason) {
              this.propertyPath = propertyPath;
              this.rejectedValue = rejectedValue;
              this.reason = reason;
          }
      
          public static List<ConstraintViolationError> of(
                  Set<ConstraintViolation<?>> constraintViolations) {
              return constraintViolations.stream()
                      .map(constraintViolation -> new ConstraintViolationError(
                              constraintViolation.getPropertyPath().toString(),
                              constraintViolation.getInvalidValue().toString(),
                              constraintViolation.getMessage()
                      )).collect(Collectors.toList());
          }
       }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    @RestControllerAdvice
    public class GlobalExceptionAdvice {
      @ExceptionHandler
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public ErrorResponse handleMethodArgumentNotValidException(
              MethodArgumentNotValidException e) {
          final ErrorResponse response = ErrorResponse.of(e.getBindingResult());
      
          return response;
      }
    }
    

비즈니스 로직 예외 처리

Checked Exception와 Unchecked Exception

  • Checked Exception
    • 발생한 예외를 잡아서(catch) 체크한 후에 해당 예외를 복구 or 회피 등의 어떤 구체적인 처리 하는 예외
    • 대표적 체크예외 : ClassNotFoundException
  • Unchecked Exception
    • 예외를 잡아서(catch) 해당 예외에 대한 어떤 처리를 할 필요가 없는 예외
    • RuntimeException을 상속하여 직접 언체크 예외 가능
    • 대표적 언체크 예외 : NullPointerException, ArrayIndexOutOfBoundsException

개발자가 의도적으로 예외를 던질 수(throw) 있는 상황

  • 백엔드 서버와 외부 시스템과의 연동에서 발생하는 에러 처리
  • 시스템 내부에서 조회하려는 리소스(자원, Resource)가 없는 경우

의도적인 예외 던지기/받기(throw/catch)

1
2
3
4
5
public Member findMember(long memberId) {
    //TODO
    
    throw new RuntimeException("Not found member");
}
  • 예외 던지기(throw)

1
2
3
4
5
6
7
@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
    System.out.println(e.getMessage());
    
    return null;
}
  • 예외 잡기(catch)
This post is licensed under CC BY 4.0 by the author.