[TIL] 22.12.23 (좌충우돌 전역예외처리)

조성현·2022년 12월 24일
0

TIL 요약 (문시해알이 무엇인가요?)

  • 문: 이미 한~참 진행된 스프링 개인과제(총 3번의 과제가 합쳐진)에 전역 예외처리(Global ExceptionHandling)를 해야했다.

  • 시: Error Code Enum부터 작성하려 했으나, 제한된 시간으로 인해 일단 작성된 코드를 기반으로 Global ExceptionHandling을 공부하고 적용하는 것을 목표로 진행하였다.

  • 해: @RestControllerAdvice를 바탕으로 전역예외처리를 성공?! 한 줄 알았으나 .. 한 가지 문제가 더 있었다~!

  • 알: Global ExceptionHandling를 위해 필요한 다양한 어노테이션들을 알 수 있었고, 한 걸음 더 나아가 throw하는 법에 대해서도 더 공부할 수 있었다.


오늘 만난 문제

스프링 숙련 과제 LV2에서 예외처리에 대한 명세를 만났다.

  • 명확히 명시된 것은 아니었지만, 이전부터 궁금해서 찾아봐두었던 전역예외처리를 도전해보기로 결심했다.

내가 시도해본 것들

[스프링부트] @ExceptionHandler를 통한 예외처리

위 글에 있는 것처럼 enum ErrorCode을 작성하여 한 곳에서 모든 에러코드들을 관리하고 싶었다.

  • (기존에는 RuntimeException, IllegalArgumentException 을 상속받은 예외들이 주루룩 있는 상태)
// 내가 선언한 Exception 예시
public class CommentNotExistException extends IllegalArgumentException{
    public CommentNotExistException(){super("댓글이 존재하지 않습니다");}
}

그러나 전반적인 코드들을 다 뒤엎어야한다는 점, 이미 작성된 코드들에 대해서 전역예외처리를 해보는 경험도 의미있을 것이라는 판단 하에

  • 지금 상태에서 Global ExceptionHandling을 해보기로 결정하였다.

어떻게 해결했는가

GlobalExceptionHandler 작성

위 글에서 전역예외처리를 하는 것을 보면서 전반적인 매커니즘을 이해했다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ CustomException.class })
    protected ResponseEntity handleCustomException(CustomException ex) {
        return new ResponseEntity(new ErrorDto(ex.getErrorCode().getStatus(), ex.getErrorCode().getMessage()), HttpStatus.valueOf(ex.getErrorCode().getStatus()));
    }

@ExceptionHandler({ CustomException.class })

  • 내가 선언한 CommentNotExistException도 이런식으로 잡을 수 있겠다는 점
  • .java가 아니라 .class로 쓴다는 점에서 예외처리가 진행되는 시점에 대한 생각을 할 수 있었고,
  • return 쪽을 보면서 글 윗부분에 작성했던 enum ErrorCode를 이렇게 값을 뽑아서 리턴해주는 식으로 사용하면서 하나의 핸들러로 모든 CustomException을 커버할 수 있구나~ 라는 점을 배울 수 있었다.

바로 좌충우돌 전역예외처리를 시작했다.

  • 일단 생각의 흐름대로 코드를 작성해 보았다.
@ExceptionHandler({UserNotExistException.class, MissmatchPasswordException.class})
protected ResponseEntity<String> handleLoginException(UserNotExistException e1, MissmatchPasswordException e2) {
    if (e1.getMessage().isEmpty()){
        return new ResponseEntity<>(e2.getMessage(), HttpStatus.UNAUTHORIZED);
        }
    return new ResponseEntity<>(e1.getMessage(), HttpStatus.UNAUTHORIZED);
}

당연히! 원하는대로 핸들링이 되지 않았다.

  • 하나의 Exception만 잡는 핸들러는 잘 작동했으나 위 핸들러는 작동하지 않았다.
  • parameter에 e1, e2가 동시에 들어올 수가 없으니 작동하지 않는게 당연했다.
    (생각나는 대로 한다고, 기본 중의 기본을 생각하지 못하다니...)
  • 다시 차분하게 코드를 수정했다.
@ExceptionHandler({UserNotExistException.class, MissmatchPasswordException.class})
protected ResponseEntity<String> handleLoginException(IllegalArgumentException e) {
    return new ResponseEntity<>(e.getMessage(), HttpStatus.UNAUTHORIZED);
}

잘 잡힌다!

  • @ExceptionHandler 옆에 쓰는 '내가 잡길 원하는 예외'들은 자세하게 써야한다.
  • parameter의 Type은 모든 예외들을 포괄하는 RuntimeException으로 설정해두는 것이 좋다.
  • HTTP Status Code는 무작정 2XX, 4XX을 던지는 것보다 의미에 맞는 Code를 보내주는 연습을 하자.

무엇을 알게 되었는가

1. 전역 예외처리(Global ExceptionHandling)하는 법을 알게되었다.

  • enum ErrCode를 작성하고, 이를 활용해서 전역예외처리하는 방법과,
  • 기존에 작성된 코드들(많은 예외클래스 들이 존재하는)에서 전역예외처리하는 방법을 모두 배울 수 있었다.

2. HTTP Status Code에 대해 더욱 생각하고 return하게 되었다.

3. 던지기(Throw)도 잘 던져야하고, 받기(Handling)도 잘 받아야 된다는 점을 배웠다.

public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (SecurityException | MalformedJwtException e) {
        log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
    } catch (ExpiredJwtException e) {
        log.info("Expired JWT token, 만료된 JWT token 입니다.");
    } catch (UnsupportedJwtException e) {
        log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
    } catch (IllegalArgumentException e) {
        log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
    }
    return false;
    }

    // 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

JWT 강의에서 가르쳐준 방식에서는

  • validateToken()으로 검증하고, getUserInfoFromToken()으로 값을 꺼내오는 방식으로 진행되었었다.
  • 사실상 같은 코드를 2번 반복하는 것이기도 하고, 친구를 통해 JWT verify(디코딩하는)비용이 비싸다는 얘기도 들은터라 위 매커니즘을 축약하고 싶었다.
  • 그래서 아래와 같이 유저정보를 꺼내는 과정에서 예외가 발생할 경우 RuntimeException을 상속한 AuthException을 throw하도록 설계하였다.
public Claims getUserInfoFromToken(String token) {
    try {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    } catch (Exception e) {
        log.info("AuthException");
        throw new AuthException();
    }
}


참고문헌(Reference)

SpringDocs- Exceptions
SpringDocs- Controller-Advice
테코블-전역예외처리
ResponseEntity는 왜 사용하는 것이며 @RestControllerAdvice는 무엇일까.
exceptionHandling
@ExceptionHandler를 통한 예외처리

profile
맛있는 음식과 여행을 좋아하는 당당한 뚱땡이

0개의 댓글