Controller
를 작성하다 보면 예외처리를 반복적으로 작성해야할 때가 있다.
예를 들어 IllegalArgumentException
일 경우 400(BadRequest)
를 응답한다거나 알 수 없는 Exception
의 경우 500(INTERNAL_SERVER_ERROR)
를 발생시키는 등..
@GetMapping("/api/v1/members")
public ResponseEntity getAllMember(Pageable pageable) {
Page<Member> memberPage;
// 반복적으로 발생하는 Try-Catch
try {
memberPage = memberFindService.getAllMemberPage(pageable);
} catch (RuntimeException re) {
return ResponseEntity.badRequest().build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return ResponseEntity.ok().body(MemberAdminDto.Info.of(memberPage));
}
위와 같이 try-catch
를 반복적으로 사용하는 경우 내 손가락도 아프고.. 가독성도 떨어진다..
내가 좋아하는 클린코드 라는 책에는 이런 구절이 있다.
try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다.
Controller에서 발생하는 반복적인 예외처리를 피하기 위해 Spring의 @ExceptionHandler
를 사용해 보자
@ExceptionHandler
는 @Controller, @RestController가 적용된 Bean내에서 발생하는 예외를 잡아서 하나의 메서드에서 처리해주는 기능을 한다.
({ })
안에 처리할 예외 클래스를 넣은 후 매서드로 처리 내용을 구현하면 된다.
@ControllerAdvice
는 모든 @Controller 즉, 전역에서 발생할 수 있는 예외를 잡아 처리해주는 annotation이다.
@ControllerAdvice
@Slf4j
public class ExceptionController {
// 400
@ExceptionHandler({
MemberJoinException.class,
RuntimeException.class
})
public ResponseEntity<Object> BadRequestException(final RuntimeException ex) {
log.warn("error", ex);
return ResponseEntity.badRequest().body(ex.getMessage());
}
// 401
@ExceptionHandler({ AccessDeniedException.class })
public ResponseEntity handleAccessDeniedException(final AccessDeniedException ex) {
log.warn("error", ex);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
}
...
// 500
@ExceptionHandler({ Exception.class })
public ResponseEntity<Object> handleAll(final Exception ex) {
log.info(ex.getClass().getName());
log.error("error", ex);
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@ControllerAdvice
를 통해 모든 컨트롤러에서 발생하는 예외를 잡고 @ExceptionHandler
를 통해 발생하는 예외의 종류마다 처리할 매서드를 정의한다.
// Exception Controller 설정 후 코드
@GetMapping("/api/v1/admin/members") public ResponseEntity getAllMember(Pageable pageable) {
// RuntimeException 발생 시 알아서 400에러 리턴, Exception 발생 시 500 리턴
Page<Member> memberPage = memberFindService.getAllMemberPage(pageable);
return ResponseEntity.ok().body(MemberAdminDto.Info.of(memberPage));
}
설정이 완료되면 Controller
에 예외를 처리하는 코드를 작성하지 않아도 알아서 설정해놓은 대로 예외를 처리해준다.
물론 직접 try/catch
를 통해 예외를 처리하면 직접 처리한 것이 우선순위로 작동하게 된다.