Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
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
Archives
Today
Total
관리 메뉴

JustDoEat

효율적으로 예외 처리 해보기,Custom Exception과 GlobalErrorHandler로 예외 관리하기 2편 본문

카테고리 없음

효율적으로 예외 처리 해보기,Custom Exception과 GlobalErrorHandler로 예외 관리하기 2편

kingmusung 2024. 9. 8. 19:33

개요.

 

효율적으로 예외 처리 해보기,Custom Exception과 GlobalErrorHandler로 예외 관리하기 1편

개요. GitHub - woosungking/Good-Night-3rd-Hackathon: Hackathon 이후 후속조치 및 추가기능 구현 Docker 연동 및 프론트Hackathon 이후 후속조치 및 추가기능 구현 Docker 연동 및 프론트, 백엔드 통합 테스트를 위한

kingmusung.tistory.com

 

1편에서는 예외처리를 효율적으로 하는 방법에 대한 큰 그림을 그렸었다.

2편에서는 예외가 핸들러에 의해 처리가 되는 과정을 나름대로(?) 순서도 느낌으로 기록해 보겠다.


1. 소원조회 시 없는 ID값을 입력 후 일부로 예외를 만듦.

public Wish getWish(Long id) {
        String sql = "SELECT * FROM wishes WHERE id = ?";
        try {
            return jdbcTemplate.queryForObject(sql, new WishRowMapper(), id);
        } catch (EmptyResultDataAccessException e) {
            throw new WishException.NotFoundWishException();
        }
    }

 

WishException이라는 추상클래스 안에 있는, NotFoundWishException 객체를 생성

 

"1편에서 설명을 했던 것처럼 예외를 상위 클래스로 던지고 던져서 마지막에 처리"

 

throw new WishException.NotFoundException(); 이 실행이 되면서

 

상위 클래스 호출

package com.hackaton.Good_Night_3rd_Hackathon_Backend.domain.wish.exception;

import com.hackaton.Good_Night_3rd_Hackathon_Backend.common.error.BusinessException;

public abstract class WishException extends BusinessException {
    protected WishException(WishErrorCode wishErrorCode){
        super(wishErrorCode);
    }
    public static class NotFoundWishException extends WishException{
        public NotFoundWishException(){
            super(WishErrorCode.WISH_NOT_FOUND);
            //WISH_NOT_FOUND(HttpStatus.NOT_FOUND, "E001", "해당 아이디에 일치하는 소원이 없습니다.");
        }
    }
}

 

getWish()라는 메서드에서 예외를 NotFoundWishException으로 던졌다.

 

코드를 보면, NotFoundWishException은 WishException이라는 추상클래스를 상속받고 있다.

 

중간 정리를 하자면

 

1. getWish()라는 메서드에서 NotFoundWishException의 객체를 생성한다,

2. " super() " 에 의해  WishErrorCode.WISH_NOT_FOUND라는 인자를 부모클래스인 WishException에 전달을 한다.

3. WishException 또한 BusinessException을 상속을 받고 있고, "super()"에 의해 또다시 WishErrorCode.WISH_NOT_FOUND라는 인자를 부모 클래스에게 전달을 해주고 있다.

 

" WishErrorCode.WISH_NOT_FOUND 이게 뭔데 "

 

Spring 에서 ResponseEntity와 RestApiResponse로 RESTful API 응답 구조 이해하기 1탄

개요.헤커톤 프로젝트로 진짜 남들이 보면 비웃을 수도 있는 Spring Boot로 기본적인 CRUD 구현을 했다,헤커톤이 끝난 후 다른 사람들이 spring boot로 만든 프로젝트를 보면서 이건 왜 이렇게 했지 등

kingmusung.tistory.com

 

우리가 예외를 던질 때 예를 든다면.

	Map<String, Object> responseBody = new HashMap<>();
    responseBody.put("code", "E001");
    responseBody.put("status", "NOT_FOUND");
    responseBody.put("message", "소원 조회 실패");
    responseBody.put("data", wish); // 실제 데이터 (Wish 객체)

    return ResponseEntity.status(HttpStatus.OK).body(responseBody);

 

API 요청에 대한 응답의 BODY 즉 본문 부분에 상태 및 코드를 넣어준다고 했을 때, 모든 예외마다 위와 같이 적게 된다면.

일단 코드가 엄청 길어질 뿐만이 아니라, 유지보수가 매우 어려울 것이다.

 

위와 같은 예외를 5개의 클래스에 적는다고 만약! 가정을 하자, 예외에 대한 Message를 수정을 하고 싶다면 5개의 클래스에서 저 부분을 찾아서 전부 고쳐야 한다.

 

우리는 사람이기 때문에 실수를 범 할 수도 있고 너무 위험한 방법이다.

 

좋은 방법은 아래와 같이

 

Enum에 발생할 수 있는 예외에 대한 정의를 해놓고 필요할 때 가져다 쓴다면 일관된 형식을 유지할 수 있고 유지보수 또한 쉬워지겠다.

 

@Getter
public enum WishErrorCode implements ErrorCode { // @Getter 어노테이션으로 인해 오버라이딩 별도로 안해도 됩니당.

    WISH_NOT_FOUND(HttpStatus.NOT_FOUND, "E001", "해당 아이디에 일치하는 소원이 없습니다.");
	
    private HttpStatus status;
    private String code;
    private String message;

    WishErrorCode(HttpStatus status, String code, String message){
        this.status = status;
        this.code = code;
        this.message = message;
    }

};

 

WISH_NOT_FOUND 하나밖에 없지만, 추가적인 예외처리가 필요하다면 이어서 예외에 대한 정보를 적어주면 되는 것이다.

 

정리하자면 ID값에 해당하는 소원이 매치가 되지 않을 수 있다고 미리 생각을 해서 정의를 해놓은 것이다.


다시 본론으로 돌아와서

throw new WishException.NotFoundException(); 이 실행이 되면서

NotFoundException -> WishException까지 "super()"를 통해 

WishErrorCode.WISH_NOT_FOUND라는 인자를 부모 클래스에게 전달을 해주고 있다. 까지 했다.

 

오케이 최종적으로 WishException이 super를 통해 부모클래스인 businessException을 호출을 했다.

@Getter
public class BusinessException extends RuntimeException{
    private HttpStatus status;
    private String code;
    private ErrorCode errorCode;
    public BusinessException(ErrorCode errorCode){
        super(errorCode.getMessage());
        this.status = errorCode.getStatus();
        this.code = errorCode.getMessage();
        this.errorCode = errorCode;
    }
}

 

NotFoundWishException부터 WishException 까지는 생성자에 대한 정의 밖에 없었지먼, 여기서부터 필드에 멤버변수가 생긴다.

 

1편에서도 설명을 했지만 다시 한번 상기하자면

 

"왜 BusinessException 에는 필드에 멤버변수가 있는 거지?"

"message 필드도 정의해서 쓰면 되는데 왜 굳이 "super()"를 이용해서 부모한테 message를 던지는 거지?"

 

일단 예외는 최상위 클래스에서 처리하는 것이 좋다고 말을 했었다, 즉 예외를 처리할 수 있는 최대 클래스까지 도달을 한 것이다.

최상단 클래스까지 왔으니 이제 예외를 처리해야 하는데,

예외를 처리하기 위해서는 전달받은 "WishErrorCode.WISH_NOT_FOUND"는 객체의 포장지를 뜯어서 안에 내용물을 최상위 클래스의 객체의 것으로 만들어야 한다. 그래야 하므로 멤버변수가 있는 것이다.

 

최상위 클래스라는 개념이 참 포괄적이긴 하지만, RuntimeException이 될 수도 있고 더 위 Exception이 될 수 있다,

하지만 지금 여기서는 RuntimeException까지를 최상위 클래스로 보는 것이다. 

예외 메시지에 처리에 대한 일관되고, 잘 만들어진 로직이 RuntimeException이라는 클래스에 이미 있기 때문에 RuntimeException을 상속받은 BusinessException에 이중으로 상속을 할 필요가 없는 것이다.

 

정리를 하자면, 메시지에 대한 로직은 RuntimeException의 것을 쓰고, 상태, 상태코드 그 외에 예외를 처리하기 위해 필요한 정보를 자식클래스에서 정의를 해서 사용을 하기 때문에 message를 BusinessException에 멤버변수로 정의를 하지 않는 것이다.



"예외를 상위 클래스까지 전달했으니 핸들러를 이용해 처리를 해보자"

 

 

예외를 열심히 정의했는데 처리를 안 해주면 아래와 같이.

 

응답을 받은 입장에서는 무엇이 문제인지 모른다, 위  응답값만 보았을 때 무엇이 문제인지 한 번에 알겠는가?

그러기 때문에 예외에 대한 처리를 장식하는 Handler를 정의해서 사용하는 것이다.

 

물론 내가 만든 예외가 서버 측 콘솔에는 나온다, 하지만 그걸 원하는 건 아니지 않은가.

 

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<RestApiResponse> handleBusinessException(BusinessException e){
        RestApiResponse response = new RestApiResponse(e.getErrorCode());
        return ResponseEntity.status(e.getStatus()).body(response);
    }
}

 

각 어노테이션에 대한건 1편에서 소개를 했으니 생략하겠다.

 

핸들러 클래스를 보면 handleBusinessException이라는 메서드가 보일 것이고, 이 메서드의 인자로

 

BusinessException 타입의 e라는 인자를 받는 것을 볼 수 있을 것이다.

 

예외가 BusinessException 클래스까지 도달을 했다면 핸들러가 자동적으로 handleBusinessException이라는 메서드에 인자까지 집어넣어서 실행을 시켜주는 것이다.

 

이로 인해 예외를 처리하는 순서까지 살펴봄으로 글을 마치도록 하겠다.

 

 

아래는 RestApiResponse를 사용해서 응답값을 통일화시켜 주는 방법에 대한 글이다! 내가 공부하고 이해한 내용을 바탕으로 적었으니 같이 보면 좋을 듯싶다!

 

1편

 

 

Spring 에서 ResponseEntity와 RestApiResponse로 RESTful API 응답 구조 이해하기 1탄

개요.헤커톤 프로젝트로 진짜 남들이 보면 비웃을 수도 있는 Spring Boot로 기본적인 CRUD 구현을 했다,헤커톤이 끝난 후 다른 사람들이 spring boot로 만든 프로젝트를 보면서 이건 왜 이렇게 했지 등

kingmusung.tistory.com

 

2편

 

Spring 에서 ResponseEntity와 RestApiResponse로 RESTful API 응답 구조 이해하기 2탄

개요. 헤커톤 프로젝트로 진짜 남들이 보면 비웃을 수도 있는 Spring Boot로 기본적인 CRUD 구현을 했다,헤커톤이 끝난 후 다른 사람들이 spring boot로 만든 프로젝트를 보면서 이건 왜 이렇게 했지 등

kingmusung.tistory.com