멍두의 개발새발
[스프링] @RestControllerAdvice 리팩토링 본문
@RestControllerAdvice
@ResponseBody와 @ControllerAdvice를 결합한 Annotaiton으로 전역으로 에러를 처리하고 자동으로 http body에 예외 정보를 넣어준다.
@Slf4j
@RestControllerAdvice
public class MainExceptionResolverController {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ExceptionDto> illegalArgumentExceptionHandler(IllegalArgumentException ex){
ExceptionDto exceptionDto = new ExceptionDto("IllegalArgumentException", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionDto);
}
}
이런 식으로 작성하면 IllegalArgumentException이 터졌을 때 사용자는 400 code와 함께
{ "exceptionName": "IllegalArgumentException", "message": "잘못된 요청입니다" }
로 응답을 받을 수 있다.
😵 그런데 점점 exception이 다양해지면서 중복되는 코드가 많아졌다.
💻 MainExceptionResolverController
@Slf4j
@RestControllerAdvice
public class MainExceptionResolverController {
@ExceptionHandler(AiConnectionException.class)
public ResponseEntity<ExceptionDto> aiConnectionExceptionHandler(AiConnectionException ex){
ExceptionDto exceptionDto = new ExceptionDto("AiConnectionException", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exceptionDto);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ExceptionDto> userNotFoundExceptionHandler(UserNotFoundException ex){
ExceptionDto exceptionDto = new ExceptionDto("UserNotFoundException", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exceptionDto);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ExceptionDto> illegalArgumentExceptionHandler(IllegalArgumentException ex){
ExceptionDto exceptionDto = new ExceptionDto("IllegalArgumentException", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionDto);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ExceptionDto> runtimeExceptionHandler(RuntimeException ex){
ExceptionDto exceptionDto = new ExceptionDto("IllegalArgumentException", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionDto);
}
}
으악 내눈 👎
이 코드를 리팩토링 해보자!
- 최대한 중복을 없애보자
- 추후에 에러를 추가할 때를 고려하여 확장이 편한 코드로 만들어보자
- 가독성을 높히자
중복을 없애기 위해 아래 부분을 메소드로 추출하자
ExceptionDto exceptionDto = new ExceptionDto("IllegalArgumentException", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionDto);
이때 메소드마다 필요한 className과 httpStatus이다.
httpStatus는 인자로 받고 className은 exception.getClassName()
으로 추출하자.
💻 extractClassName
className을 추출하는 함수
protected String extractClassName(String fullClassName){
String[] classNameParts = fullClassName.split("\\.");
return classNameParts[classNameParts.length - 1];
}
- 파라미터
String fullClassName = exception.getClassName().toString()
- ex )
com.potato.balbambalbam.main.cardList.exception.CategoryNotFoundException
- ex )
- 이때 exception이름만 추출하기 위해 String을 . 기준으로 split하고 제일 마지막 index를 가져온다.
💻 exceptionHandler
protected ResponseEntity<ExceptionDto> exceptionHandler(Exception ex, HttpStatus httpStatus){
String className = extractClassName(ex.getClass().toString());
String exMessage = ex.getMessage();
log.info("[ERROR] ["+ className + "]:" + exMessage);
return ResponseEntity.status(httpStatus).body(new ExceptionDto(className, exMessage));
}
- 이제 exception은 모두 이 함수로 처리해주면 된다.
- log처리 + resposneEntity 생성
1차 리팩토링
@Slf4j
@RestControllerAdvice
public class MainExceptionResolverController {
@ExceptionHandler(AiConnectionException.class)
public ResponseEntity<ExceptionDto> aiConnectionExceptionHandler(AiConnectionException ex){
return exceptionHandler(ex, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ExceptionDto> userNotFoundExceptionHandler(UserNotFoundException ex){
return exceptionHandler(ex, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ExceptionDto> illegalArgumentExceptionHandler(IllegalArgumentException ex){
return exceptionHandler(ex, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ExceptionDto> runtimeExceptionHandler(RuntimeException ex){
return exceptionHandler(ex, HttpStatus.BAD_REQUEST);
}
protected ResponseEntity<ExceptionDto> exceptionHandler(Exception ex, HttpStatus httpStatus){
String className = extractClassName(ex.getClass().toString());
String exMessage = ex.getMessage();
log.info("[ERROR] ["+ className + "]:" + exMessage);
return ResponseEntity.status(httpStatus).body(new ExceptionDto(className, exMessage));
}
protected String extractClassName(String fullClassName){
String[] classNameParts = fullClassName.split("\\.");
return classNameParts[classNameParts.length - 1];
}
}
중복되는 코드가 거의 사라졌으나 여전히 httpStatus가 동일하면 중복되는 코드가 보인다.
떼잉! 😞
하나의 @ExceptionHandler로 여러 exception을 처리하자
@ExceptionHandler({excpeiton1.class, exception2.class, ... })
리팩토링 성공!
@Slf4j
@RestControllerAdvice
public class MainExceptionResolverController {
@ExceptionHandler({AiConnectionException.class, UserNotFoundException.class, CategoryNotFoundException.class, CardNotFoundException.class})
public ResponseEntity<ExceptionDto> notFoundExceptionHandler(Exception ex){
return exceptionHandler(ex, HttpStatus.NOT_FOUND);
}
@ExceptionHandler({IllegalArgumentException.class, RuntimeException.class})
public ResponseEntity<ExceptionDto> badRequestExceptionHandler (IllegalArgumentException ex){
return exceptionHandler(ex, HttpStatus.BAD_REQUEST);
}
protected ResponseEntity<ExceptionDto> exceptionHandler(Exception ex, HttpStatus httpStatus){
String className = extractClassName(ex.getClass().toString());
String exMessage = ex.getMessage();
log.info("[ERROR] ["+ className + "]:" + exMessage);
return ResponseEntity.status(httpStatus).body(new ExceptionDto(className, exMessage));
}
protected String extractClassName(String fullClassName){
String[] classNameParts = fullClassName.split("\\.");
return classNameParts[classNameParts.length - 1];
}
}
리팩토링의 목표였던
- 최대한 중복을 없애보자
- 추후에 에러를 추가할 때를 고려하여 확장이 편한 코드로 만들어보자
- 가독성을 높히자
를 모두 성공했다!
httpStatus에 따라 함수 이름을 짓고 해당 code를 반환하는 error exception들은 모두 @ExceptionHandler
안에 넣어주기만 하면 된다.
굳!
💬 이글을 작성한 이유
작동하는 코드를 짜기 급급해서 이런 리팩토링은 처음이다
내 코드가 보기에도 에러를 추가할 때도 너무 불편해서 리팩토링을 해보니 짜릿했다
뭔가 내가 뚝딱뚝딱 만든게 두 눈에 바로 보이는 느낌🔨
특히 확장이 편한 코드로 만든 부분이 젤 만족스러웠다.
이기분을 잊지않기 위해 이 글을 쓴다
'Programming > Spring' 카테고리의 다른 글
[스프링] DTO에 @NoArgsConstructor와 @Gettter이 필요한 이유 (0) | 2024.07.30 |
---|---|
[스프링] - findByIN절, jdbc batchUpdate (bulk Insert)로 쿼리 개선 (0) | 2024.05.29 |
[스프링] [에러해결] 모든 http response의 한글 깨짐을 한방에 해결하기 (0) | 2024.04.30 |
[스프링] WAS WebApplicationServer란? (0) | 2024.04.10 |