ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [AA] CustomExeption
    Spring Boot 2024. 5. 23. 11:04

    회사에서 신규프로젝트에 투입을 하게 되어서

    공통프레임워크 개발에 필요한 Exception 전략을 어떻게 처리할까

    서칭을 하다 알게된 Exception을 custom할 수 있다는 것을 알게 되었고 

    프로젝트에 적용한 내 custom exception을 기록해보겠다

    controller 메서드 마다 try-catch문을  중복으로 넣어지면 코드가 길어지니 Exception으로 처리해보겠다

     

    우선 Exception 처리 디렉토리 구성은 이렇게 만들었다.

     

     

    우선 에러코드 관리는 Enum 파일을 만들어서 관리하기로 하고 

    Enum파일을 만들었다

    현재 Enum에는 3가지 정도의 에러가 있는데 처음 만들어보는 신규 프로젝트기도 하고 

    AA는 처음이라 어떤 에러가 예상이 되지 않아서 일단은 3개정도로 구분해놓고

    로직 처리하면서 차후 에러를 추가할 예정이다

     

    Code Enum으로 관리가 필요한 이유 :

    클라이언트 단에서 Message를 전달 받아서 예외를 처리하는 것은 클라이언트 단과 서버 단이 불필요하게 결속됨을 의미하고 추후 변경이 어려워진다.

    따라서 서비스에서 정의한 Code가 필요하다.

     

    CustomErrorCode

    import lombok.Getter;
    
    @Getter
    public enum CustomErrorCode {
        UNKNOWN("400", "알 수 없는 에러가 발생했습니다."),
        GENERIC_ERROR("500", "일반 오류가 발생했습니다."),
        VALIDATION_ERROR("500", "유효성 검사 실패"),
        SPECIFIC_API_ERROR("501", "특정 API 오류");
    
        private final String customErrorCode;
        private final String statusMessage;
    
    
    	// 생성자
        CustomErrorCode(String customErrorCode, String statusMessage) {
            this.customErrorCode = customErrorCode;
            this.statusMessage = statusMessage;
        }
    
    	
        public String getCustomErrorCode() {
            return customErrorCode;
        }
    
        public String getDetailMessage() {
            return statusMessage;
        }
    }

     

    @Getter어노테이션만 만들어놓고

    생성자는 따로 코드로 작성해주었다

    CustomErrorResponse

    import lombok.*;
    
    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class CustomErrorResponse {
        private CustomErrorCode status;
        private String statusMessage;
    
    }

     

    CustomExeption

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class CustomException extends RuntimeException {
    
        /** class version id number : default */
        private static final long serialVersionUID = 1L;
        /** 발생한 에러코드*/
        private CustomErrorCode customErrorCode;
        /** 에러 메세지*/
        private String detailMessage;
    
        public CustomException(){}
    
        public CustomException(String detailMessage){
            super(detailMessage);
            this.detailMessage = detailMessage;
        }
    
        public CustomException(String detailMessage, long time){
            super(detailMessage);
            this.detailMessage = detailMessage;
        }
    
        public CustomException(CustomErrorCode customErrorCode) {
            super(customErrorCode.getStatusMessage()); // runtimeException
            this.customErrorCode = customErrorCode;
            this.detailMessage = customErrorCode.getStatusMessage();
        }
    
    
    
    }

     

    RuntimeException 상속받아서 RuntimeException lib에 있는 메서드들을 활용해서 

    필요한 메서드들을 오버로딩 하였다

     

    CustomExeptionHandler

    import jakarta.servlet.http.HttpServletRequest;
    import lombok.extern.log4j.Log4j2;
    import org.daeng2go.daeng2go_server.common.response.ResponseDTO;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
    
    @RestControllerAdvice
    @Log4j2
    public class CustomExceptionHandler {
    
        @ExceptionHandler(CustomException.class)
        public CustomErrorResponse handleException(
                CustomException e,
                HttpServletRequest request
        ) {
            log.error("errorCode : {}, url {}, message: {}",
                    e.getCustomErrorCode(), request.getRequestURI(), e.getDetailMessage());
    
            return CustomErrorResponse.builder()
                    .status(e.getCustomErrorCode())
                    .statusMessage(e.getDetailMessage())
                    .build();
        }
    
    }

     

     

    @RestControllerAdvice 

    ControllerAdvice를 이용함으로써 다음과 같은 이점을 누릴 수 있다.

    • 하나의 클래스로 모든 컨트롤러에 대해 전역적으로 예외 처리가 가능함
    • 직접 정의한 에러 응답을 일관성있게 클라이언트에게 내려줄 수 있음
    • 별도의 try-catch문이 없어 코드의 가독성이 높아짐

    CustomExeptionHandler에 @RestControllerAdvice 어노테이션을 활용해서 예외처리를 하고,

    클래스 안에 메서드 handlerException -> @ExceptionHandler 어노테이션을 활용했다.

     

    @ExceptionHandler가 하나의 클래스에 대한것이라면, @ControllerAdvice,@RestControllerAdvice는 모든 Controller

    즉 전역에서 발생할 수 있는 예외를 캐치해 처리해주는 Annotation이다.

     

    클래스파일을 만들어 어노테이션을 붙여주기만 하면 된다. 

    @RestControllerAdvice와 @ControllerAdvice가 존재하는데 @RestControllerAdvice안에는 @ResponseBody가 있다는 차이가 있다.

    @RestController와 @Controller의 차이처럼 @ControllerAdvice는 뷰리졸버를 통해 에러페이지로 리다이렉트하고 싶을때 쓰면되고

    @RestControllerAdvice는 API서버여서 에러응답객체나 메시지를 리턴해야할때 사용하면된다.

    (즉 예외발생시 응답처리를 어떻게할거냐에 따라 골라쓰면된다)

    (예외 발생시 @ControllerAdvice, @RestControllerAdvice 클래스로 넘어와 응답처리를 해준다고 생각하자)

     

    참고

    @ControllerAdvice 를 쓰던 @RestControllerAdvice를 쓰던간에 @Controller,@RestController 에서 발생하는 예외를 캐치할수있다. "@RestControllerAdvice는 @RestController의 예외만 캐치한다"는 아니라는것

    + 하지만 @RestControllerAdvice(annotation = "RestController.class") 와 같이 적용되는 어노테이션을 설정할수도 있다.

     


    테스트를 해보자.

    TestController.java

    @RequestMapping(value = "/add", method = RequestMethod.POST)
        public ResponseEntity setBreed(@RequestBody BreedDTO breedDTO) {
                //throws CustomException {
    /*
            if (breedDTO == null) {
                throw new CustomException(CustomErrorCode.UNKNOWN);
            }*/
    
            ResponseDTO.ResponseDTOBuilder builder = ResponseDTO.builder();
    
            breedService.setBreed(breedDTO);
    
            builder.code(ResponseStatus.SUCCESS_CODE).msg(ResponseStatus.SUCCESS_MSG);
    
            return ResponseEntity.ok(builder.build());
        }

    위 주석처리된 부분을 주석을 지우면 list가 null 일경우에 exception 처리가 되어서 

    로그에 찍혀 보이게된다

Designed by Tistory.