@ControllerAdvice
를 사용하여 예외처리 로직 분리하기
과제 프로젝트를 개선하면서 받은 피드백 중 하나는 @ControllerAdvice
를 통해 예외 처리 로직을 분리하라는 것이었습니다.
아래 코드를 보면 예외를 잡고 발생하는 예외마다 다른 Http Status code를 반환했습니다.
예시 코드
@PostMapping
fun requestApartmentToBeMoved(@RequestBody apartmentRequest: ApartmentRequest, httpSession: HttpSession)
: ResponseEntity<String> {
logger.info("apartmentRequest:{}", apartmentRequest)
z
try {
val apartment = apartmentViewService.getApartment(apartmentRequest.toEntity())
httpSession.setAttribute("apartment", apartment)
} catch (e: NotExistsApartmentException) {
logger.error("error : {}", e.msg)
return ResponseEntity(HttpStatus.UNPROCESSABLE_ENTITY)
} catch (e: AlreadyReservedException) {
logger.error("error: {} ", e.msg)
return ResponseEntity(HttpStatus.CONFLICT)
}
return ResponseEntity(HttpStatus.OK)
}
제가 보기에도 try - catch
블록이 있는 함수는 이 함수가 정말로 해야 하는 일이 무엇인지 쉽게 알 수 없었고, 여러 핸들링 함수에 걸쳐 예외 처리 로직이 중복되는 문제도 있었습니다. 예외 처리 같은 핵심 로직을 벗어난 부가 로직을 횡단 관심(cross-cutting concern)이라고 부른다는 사실을 알게되었습니다. 이런 횡단 관심사를 분리하기 위해 스프링은 AOP(Aspect oriented programming)를 제공합니다. 피드백 받은 @ControllerAdvice
또한 AOP가 제공하는 애노테이션 중 하나입니다. 이는 발생할 수 있는 예외를 한 곳에 모아서 전역적으로 처리할 수 있도록 하는 기능입니다. @ExceptionHandler
와 함께 사용해서 try-catch
를 사용해야 했던 문제를 해결할 수 있었습니다.
Controller Advice적용
@RestControllerAdvice
class ExceptionHandlerController {
companion object : Logger
@ExceptionHandler(value = [AlreadyReservedException::class, UnreservedUserException::class])
fun conflictException(e: AlreadyReservedException) : ResponseEntity<String> {
logger.error(e.msg)
return ResponseEntity(HttpStatus.CONFLICT)
}
@ExceptionHandler(OutRangeDateException::class)
fun outRangeDateExceptionHandler(e: OutRangeDateException): ResponseEntity<String> {
logger.error(e.msg)
return ResponseEntity(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
}
@ExceptionHandler(NotFoundException::class)
fun notFoundExceptionHandler(e: NotFoundException): ResponseEntity<String> {
logger.error(e.message)
return ResponseEntity(HttpStatus.NO_CONTENT)
}
@ExceptionHandler(value = [NotExistsApartmentException::class, InvalidRequestStateException::class])
fun notExistsApartmentExceptionHandler(e: NotExistsApartmentException): ResponseEntity<String> {
logger.error(e.msg)
return ResponseEntity(HttpStatus.UNPROCESSABLE_ENTITY)
}
}
@ControllerAdvice
를 적용한 뒤 컨트롤러 함수
@PostMapping
fun requestApartmentToBeMoved(@RequestBody apartmentRequest: ApartmentRequest, httpSession: HttpSession)
: ResponseEntity<String> {
logger.info("apartmentRequest:{}", apartmentRequest)
val apartment = apartmentViewService.getApartment(apartmentRequest.toEntity())
httpSession.setAttribute("apartment", apartment)
return ResponseEntity(HttpStatus.OK)
}
발생할 수 있는 예외를 @ExceptionHandler
의 프로퍼티로 설정하고 예외가 발생하면 로그를 찍고 적절한 HTTP상태 코드를 반환하도록 처리했습니다. 이렇게 @ControllerAdvice
를 적용한 다음 기존 컨트롤러의 코드는 더 짧고 명확해졌습니다.
'Spring' 카테고리의 다른 글
ConstraintValidator 그리고 유효성 검사에 대한 고민.. (0) | 2022.05.16 |
---|---|
Spring ArgumentResolver (0) | 2022.05.12 |
초간단 Spring Scheduler 적용 (0) | 2021.03.02 |
Springboot + JPA + Querydsl로 좋아요 기능 만들기 1 - 등록 (10) | 2021.02.07 |
[Security] 현재 로그인한 사용자 정보 가져오기 (0) | 2021.01.23 |