Trong Spring MVC chúng ta có rất nhiều cách để đặt HTTP status code cho một HTTP response. Tuy nhiên trong bài viết này chúng ta sẽ cùng nhau tìm hiểu cách sử dụng @ResponseStatus annotation để đạt được điều tương tự.
@ResponseStatus trên controller method
Khi một endpoint trên controller thực thi thành công, mặc định spring sẽ gán HTTP status 200(OK) cho response. Tuy nhiên chúng ta có thể chỉ định một HTTP status code cụ thể cho response trên một controller method với @ResponseStatus annotation.
@ResponseStatus(HttpStatus.I_AM_A_TEAPOT) void teaPot() {}
Hoặc khi chúng ta muốn thông báo một error nào đó thì chúng ta có thể cung cấp mã lỗi kèm theo lý do cụ thể như sau:
@ResponseStatus(HttpStatus.BAD_REQUEST, reason = "Some parameters are invalid") void onIllegalArgumentException(IllegalArgumentException exception) {}
NOTE: Ví dụ ở trên thông thường sẽ không được sử dụng trên các controller method, vì đâu có lý do một endpoint chưa biết thực thi thành công hay không mà đã mark nó trước là trả về mã lỗi rồi.
Lưu ý nữa là khi chúng ta gán giá trị cho thuộc tính reason, Spring sẽ gọi HttpServletResponse.sendError() để trả về một trang HTML error page cho client việc này sẽ khiến cho một REST API trở nên tồi tệ.’
Cho nên Spring chỉ sử dụng @ResponseStatus khi controller method thực thi thành công (không xảy ra một exception). Nếu có exception xảy ra trong quá trình thực thi thì nó sẽ bị chuyển đến bộ xử lý lỗi của spring, những gì từ @ResponseStatus chúng ta cung cấp sẽ bị bỏ qua.
@ResponseStatus cho error handler
Như lúc nảy mình đã nhắc đến rồi, là khi xảy ra lỗi spring sẽ chuyển đến bộ xử lý error. Tại đây chúng ta có thể sử dụng @ResponseStatus để gán HTTP status cho các exception tương ứng.
Ví dụ mình sử dụng @ControllerAdvice để bắt những exception xảy ra trong quá trình thực thi của REST API. Mỗi method bên trong APIExceptionHandler sẽ được dùng để xử lý một exception cụ thể xảy ra trong quá trình REST API xử lý request.
@ControllerAdvice public class APIExceptionHandler extends ResponseEntityExceptionHandler { private static final String INTERNAL_ERROR_MSG = "A system error has occurred"; public static final String FORBIDDEN = "User doesn't have permission to access this resources"; private static final Logger LOG = LoggerFactory.getLogger(APIExceptionHandler.class); @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Object handleConstraintViolationException(ConstraintViolationException e) { LOG.error(e.getMessage(), e); return new ErrorDTO(MessageType.ERROR, INTERNAL_ERROR_MSG, INTERNAL_ERROR_MSG); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Object handleUserException(CommonException e) { LOG.error(e.getMessage(), e); return e.getErrorDTO(); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.NOT_FOUND) public Object handleResourceNotFoundException(NotFoundException e) { LOG.error(e.getMessage(), e); return e.getErrorDTO(); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public Object handleInvalidParamException(InvalidParamException e) { LOG.error(e.getMessage(), e); return e.getErrorDTO(); } @ExceptionHandler public void handleInvalidFormatException(InvalidFormatException e) { LOG.error(e.getMessage(), e); } @ExceptionHandler public void handleMismatchedInputException(MismatchedInputException e) { LOG.error(e.getMessage(), e); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public Object handleDuplicatedException(DuplicatedException e) { LOG.error(e.getMessage(), e); return e.getErrorDTO(); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public Object handleFileStoreException(GitlabException e) { LOG.error(e.getMessage(), e); return e.getErrorDTO(); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Object handleAllExceptions(Exception e) { LOG.error(e.getMessage(), e); return new ErrorDTO(MessageType.ERROR, INTERNAL_ERROR_MSG, INTERNAL_ERROR_MSG); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.FORBIDDEN) public Object handleAccessDeniedException(AccessDeniedException e) { LOG.error(e.getMessage(), e); return new ErrorDTO(MessageType.WARNING, FORBIDDEN, FORBIDDEN); } @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.UNAUTHORIZED) public Object handleAuthenticationException(AuthenticationException e) { LOG.error(e.getMessage(), e); return new ErrorDTO(MessageType.WARNING, HttpStatus.UNAUTHORIZED.toString(), HttpStatus.UNAUTHORIZED.toString()); } @Override public ResponseEntity<Object> handleHttpMessageNotReadable( HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) { LOG.error(e.getMessage(), e); return new ResponseEntity<>(new ErrorDTO(MessageType.WARNING, Errors.PARAM_INVALID.getTitle(), Errors.PARAM_INVALID.getText()), HttpStatus.BAD_REQUEST); } }
Các bạn có thể thấy rất nhiều exception được mình tùy biến, trong đó sử dụng @ResponseStatus để đặt HTTP status code tương ứng cho từng exception.
Tóm lược
Cá nhân mình thấy xử lý @ResponseStatus để đặt HTTP status cho response rất ngắn gọn, chúng ta không cần phải viết thêm một số mã code để đặt HTTP status code response, giảm thiểu duplicate rõ ràng phải không các bạn!
Nguồn tham khảo
https://www.baeldung.com/spring-response-status