[헤이동동 #08] Exception Handling

Jiwoo Kim·2020년 12월 2일
0
post-thumbnail

☕헤이동동 : 생협 음료 원격 주문 서비스

이번 포스팅에서는 @ControllerAdvice, @ExceptionHandler를 사용한 Exception Handling 방법에 대해 설명한다.


개요

Exception은 코드 방방곡곡에서 아주 다양하게 발생한다. 이 때 handling 코드를 모든 클래스, 모든 모듈마다 심어 놓는다면 가독성은 물론 효율도 끔찍하게 떨어질 것이다.

스프링 부트에서는 이러한 문제를 해결하기 위해 @ControllerAdvice 어노테이션을 제안한다. Controller와 Custom Exception Handler에 @ControllerAdvice을 붙이면, 그 Controller 안에서 발생한 모든 Exception은 우선 handler에게 넘겨지는 것이다.


구현

CustomRuntimeException

프론트와 백 모두 새 유형의 Exception의 정보를 얻을 수 있도록 간단하게 name을 지정하고 확인할 수 있도록 인터페이스를 생성했다. 진짜 실무였다면 오류에 대한 자세한 정보를 섬세하게 지정해야 했겠지만, 우선은 구현 자체에 의미를 두기 위해 간단히 설정하고 넘어 갔다.

public interface CustomRuntimeException {

    String getNAME();
}

NoSuchUserException

실제 만든 많은 CustomException 중 하나이다.

public class NoSuchUserException extends RuntimeException implements CustomRuntimeException {

    @Getter
    private final String NAME;

    public NoSuchUserException(String message) {
        super(message);
        NAME = "NoSuchUserException";
    }
}

Controller

Controller에 @ControllerAdvice를 붙여 모든 Exception을 handler에게 넘길 수 있다.

@RestController
@ControllerAdvice
public class SomeController {...}

GlobalExceptionHandler

직접 정의하고 작성한 Exception handler는 아래와 같다. 코드가 길어 일부분만 긁어 왔다.

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    ObjectMapper objectMapper;

    private String buildResponseJson(CustomRuntimeException e, String msg) {
        Response response = Response.builder()
                .header(ResponseHeader.builder()
                        .name(e.getNAME())
                        .message(msg)
                        .build())
                .build();
        return objectMapper.valueToTree(response).toPrettyString();
    }

    @ExceptionHandler({InvalidRequestParameterException.class})
    public ResponseEntity<?> handleInvalidRequestParameterException(final InvalidRequestParameterException e) {

        String msg = e.getNAME() + ": Invalid request parameter [" + e.getMessage() + "]";
        log.error(msg);
        return ResponseEntity.badRequest().body(buildResponseJson(e, msg));
    }

    @ExceptionHandler({NoResultFromDBException.class})
    public ResponseEntity<?> handleNoResultFromDBException(final NoResultFromDBException e) {

        log.info(e.getNAME() + ": No result found on DB [" + e.getMessage() + "]");
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @ExceptionHandler({ExpiredJwtException.class, SignatureException.class, MalformedJwtException.class})
    public ResponseEntity<?> handleInvalidJwtException(final Exception e) {

        String msg = "Invalid JWT exception [" + e.getMessage() + "]";
        log.error(msg);
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }
}

Exception handler 안에서는 @ExceptionHandler 어노테이션을 붙여 처리할 Exception의 종류를 명시해주어야 한다.

참고로, handler에게도 @ControllerAdvice를 붙이는 이유는 handler가 전역에서 작동하게 하기 위함이라고 한다.


전체 코드는 Github에서 확인하실 수 있습니다.

0개의 댓글