-
[AA] CustomExeptionSpring 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 처리가 되어서
로그에 찍혀 보이게된다
'Spring Boot' 카테고리의 다른 글
[Mybatis] Cause: java.sql.SQLException: 부적합한 열 유형: 1111 (0) 2024.05.26 [AA] intercepter 세팅하기 (0) 2024.05.24 [JavaScript] 스크롤 막기 함수 (0) 2024.05.07 addFlashAttribute 활용 - 팝업알림 띄우기 (0) 2024.05.07 [Spring] 수동으로 maven dependency 추가 (0) 2024.04.17