[Spring] 예외 처리

hyoreal·2022년 9월 12일
0

[Spring]

목록 보기
13/25
post-thumbnail

예외처리 필요성

  • 예외처리 없이 유효성 검사 실패 시 응답 메세지

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

Spring MVC 예외처리

  • @ExceptionHandler 예외처리

    • 한 Controller 클래스 내에서 발생하는 예외 처리
    • Controller마다 동일하게 발생하는 예외 처리에 대한 중복 코드가 발생
    • 다양한 유형의 예외 처리에는 한계 존재
    @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

    • 따로 객체를 만들어 필드에 지정된 특정 값들로만 응답
    @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());
          }
       }
    }
    @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)

    • 예외 던지기(throw)
    public Member findMember(long memberId) {
        //TODO
        
        throw new RuntimeException("Not found member");
    }
    • 예외 잡기(catch)
    @ExceptionHandler
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
        System.out.println(e.getMessage());
        
        return null;
    }
profile
좌충우돌 코린이 성장기

0개의 댓글