Spring 예외처리

장원재·2024년 3월 12일
0

스프링

목록 보기
5/9

스프링으로 프로젝트를 진행할 때 예외처리에 대해서 이해하는게 조금 어려웠다. (알고보면 굉장히 단순한데...) 결론부터 말하면 @ExceptionHandler 어노테이션을 활용해서 프론트와 api 통신을 하면 된다.

회원 가입을 하는 상황에서 회원이 비밀번호를 입력하지 않았다고 해보자. 그러면 서버쪽 객체 필드에서 @NotBlank 빈 프로퍼티가 적용되어 있으면 500 에러가 발생할 것이다. 여기서 한가지 의문이 들 것이다. 클라이언트쪽의 실수인데 왜 서버에서 오류가 발생했다는 500 상태코드가 발생하는 걸까? 이것은 분명히 잘못됐다. 클라이언트 측의 실수 라면 400번대 상태코드를 전송해주는 것이 맞다. 이 부분을 해결해주는 것이 바로 @ExceptionHandler 어노테이션 이다.

1. 먼저 사용자가 잘못된 입력을 했다는 exception 클래스를 하나 만들어보겠다.

public class UserException extends RuntimeException {

    public UserException() {
        super();
    }

    public UserException(String message) {
        super(message);
    }

    public UserException(String message, Throwable cause) {
        super(message, cause);
    }

    public UserException(Throwable cause) {
        super(cause);
    }

    protected UserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
  • RuntimeException 을 상속받아 간단하게 구현했다.

2. 예외처리를 할때 보내줄 데이터는 아래와 같은 객체로 구현

@Data
@AllArgsConstructor
public class ErrorResult {
    private String code;
    private String message;
}

3. 예외처리 예제를 위한 컨트롤러

@GetMapping("/api2/members/{id}")
public MemberDto getMember(@PathVariable("id") String id) {

    if (id.equals("ex")) {
        throw new RuntimeException("잘못된 사용자");
    }
    if (id.equals("bad")) {
        throw new IllegalStateException("잘못된 입력 값");
    }
    if (id.equals("user-ex")) {
        throw new UserException("사용자 오류");
    }

    return new MemberDto(id, "hello" + id);
}

@Data
@AllArgsConstructor
static class MemberDto {
    private String memberId;
    private String name;
}

4. @ExceptionHandler 사용법

4-1) @ResponseStatus 와 함께 사용

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalStateException.class)
public ErrorResult illegalExHandle(IllegalStateException e) {
    log.error("[exceptionHandle] ex", e);
    return new ErrorResult("BAD", e.getMessage());
}
  • @ResponseStatus(HttpStatus.BAD_REQUEST) 는 함께 전송할 상태코드이다. 여기서 HttpStatus.BAD_REQUEST는 400이다.
  • @ExceptionHandler 에는 오류 클래스를 지정해주면 된다.
  • 만들어 놓은 ErrorResult 객체를 전송
  • postman 으로 /api2/members/bad 실행결과

4-2) ResponsEntity 사용하기

@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
     ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
     return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
  • ResponsEntity 를 사용하면 @ResponseStatus(HttpStatus.BAD_REQUEST) 어노태이션을 상용하지 않고 return 할때 두번째 파라미터에 상태코드를 넣어주면 된다.

  • ResponsEntity 클래스 들어가면 상태코드를 받는 생성자가 있다. (개인적으로 @ResponseStatus 어노테이션을 쓰는게 더 가독성이 좋은 것 같다.)

5. 예외처리와 컨트롤러 분리하기 @RestControllerAdvice

예외를 잡아서 정상 흐름으로 반환해주는 곳과 예외를 던지는 곳은 분리되는것이 좋다.
왜냐하면 동일한 예외처리가 필요한 다른 컨트롤러가 있다고 생각해보면, 동일한 예외처리 코드를 다른 컨트롤러에서 또 작성을 해줘야 한다. 스프링이 제공하는 @RestControllerAdvice 어노테이션을 활용하면 예외처리를 한곳에서 관리할 수 있게 해준다. 즉, 컨트롤러에서는 예외를 던지기만 하고, @RestControllerAdvice를 활용해서 예외를 공통적으로 처리해주는 클래스를 만들어주면 된다.

@Slf4j
@RestControllerAdvice //요기
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalStateException.class)
    public ErrorResult illegalExHandle(IllegalStateException e) {
        log.error("[exceptionHandle] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandle(UserException e) {
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandle(Exception e) {
        log.error("[exceptionHandle] ex", e);
        return new ErrorResult("EX", "내부오류");
    }
}
  • 위의 코드를 보면 하나의 클래스에서 공통적으로 예외를 처리해주는 것을 확인할 수 있다.

참고: 공통 예외 처리 흐름

  • ExceptionResolver 에서 ModelAndView 를 반환함으로써 정상 흐름으로 변경되게 한다. 따라서 500 에러가 아닌 유동적으로 statusCode를 지정하고, 이에 맞는 데이터를 보낼 수 있다.
profile
데이터 분석에 관심있는 백앤드 개발자 지망생입니다

0개의 댓글

관련 채용 정보