1. Giới Thiệu
Xử Lý Ngoại Lệ Trong Spring Boot hay Exception Handling trong Java chưa bao giờ là dễ dàng ngay cả với các lập trình viên lâu năm. Hôm nay TayJava xin đem đến cho các bạn một vài giải pháp để xử lý ngoại lệ thành công.
2. Xử lý ngoại lệ từ @RequestBody, @PathVariable, @RequestParam
Kịch bản: Chúng ta sẽ gửi request tạo mới user với trường firstName bỏ trống. Chúng ta sẽ xử lý ngoại lệ để in ra lỗi một cách rõ rằng khi các giá trị trong requestBody không hợp lệ.
– Để hứng được exception được ném ra từ hệ thống chúng ta cần tạo một Global Exception để nhận request được ném ra từ application.
@RestControllerAdvice public class GlobalExceptionHandler { /** * Handle exception when validate data * * @param e * @param request * @return errorResponse */ @ExceptionHandler({ConstraintViolationException.class, MissingServletRequestParameterException.class, MethodArgumentNotValidException.class}) @ResponseStatus(BAD_REQUEST) @ApiResponses(value = { @ApiResponse(responseCode = "400", description = "Bad Request", content = {@Content(mediaType = APPLICATION_JSON_VALUE, examples = @ExampleObject( name = "Handle exception when the data invalid. (@RequestBody, @RequestParam, @PathVariable)", summary = "Handle Bad Request", value = """ { "timestamp": "2024-04-07T11:38:56.368+00:00", "status": 400, "path": "/api/v1/...", "error": "Invalid Payload", "message": "{data} must be not blank" } """ ))}) }) public ErrorResponse handleValidationException(Exception e, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setTimestamp(new Date()); errorResponse.setStatus(BAD_REQUEST.value()); errorResponse.setPath(request.getDescription(false).replace("uri=", "")); String message = e.getMessage(); if (e instanceof MethodArgumentNotValidException) { int start = message.lastIndexOf("[") + 1; int end = message.lastIndexOf("]") - 1; message = message.substring(start, end); errorResponse.setError("Invalid Payload"); errorResponse.setMessage(message); } else if (e instanceof MissingServletRequestParameterException) { errorResponse.setError("Invalid Parameter"); errorResponse.setMessage(message); } else if (e instanceof ConstraintViolationException) { errorResponse.setError("Invalid Parameter"); errorResponse.setMessage(message.substring(message.indexOf(" ") + 1)); } else { errorResponse.setError("Invalid Data"); errorResponse.setMessage(message); } return errorResponse; } }
– Tạo ra một đối tượng Generic ResponseData để nhận dữ liệu.
public class ResponseData { private final int status; private final String message; @JsonInclude(JsonInclude.Include.NON_NULL) private T data; /** * Response data for the API to retrieve data successfully. For GET, POST only * @param status * @param message * @param data */ public ResponseData(int status, String message, T data) { this.status = status; this.message = message; this.data = data; } /** * Response data when the API executes successfully or getting error. For PUT, PATCH, DELETE * @param status * @param message */ public ResponseData(int status, String message) { this.status = status; this.message = message; } public int getStatus() { return status; } public String getMessage() { return message; } public T getData() { return data; } }
– Tạo RestController để nhận request từ client.
@RestController @RequestMapping("/user") public class UserController { @PostMapping("/") public ResponseData addUser(@Valid @RequestBody UserRequestDTO user) { System.out.println("Request add user " + user.getFirstName()); return new ResponseData<>(HttpStatus.CREATED.value(), "User added successfully,", 1); } @DeleteMapping("/{userId}") public ResponseData<?> deleteUser(@PathVariable @Min(value = 1, message = "userId must be greater than 0") int userId) { System.out.println("Request delete userId=" + userId); return new ResponseData<>(HttpStatus.NO_CONTENT.value(), "User deleted successfully"); } }
– Test tạo mới user với Postman
curl --location 'http://localhost:80/user/' \ --header 'Content-Type: application/json' \ --header 'Accept: */*' \ --data-raw '{ "firstName": "", "lastName": "Java", "email": "admin@email.com", "phone": "0123456789", "dateOfBirth": "06/05/2003", "status": "active", "gender": "male", "username": "tayjava", "password": "password", "type": "user", "addresses": [ { "apartmentNumber": "1", "floor": "2", "building": "A1", "streetNumber": "10", "street": "Pham Van Dong", "city": "Hanoi", "country": "Vietnam", "addressType": 1 } ] }'
– Test delete user với Postman
curl --location --request DELETE 'http://localhost:80/user/-10' \ --header 'Accept: */*'
3. Xử lý ngoại lệ từ RuntimeException
Kịch bản: Xử lý ngoại lệ khi chúng ta ném Exception từ tầng Service
– Tạo một customize exception được kế thừa từ RuntimeException
public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } }
– Xử lý ngoại lệ được ném ra từ ResourceNotFoundException tại GlobalExceptionHandler
/** * Handle exception when the request not found data * * @param e * @param request * @return */ @ExceptionHandler(ResourceNotFoundException.class) @ResponseStatus(NOT_FOUND) @ApiResponses(value = { @ApiResponse(responseCode = "404", description = "Bad Request", content = {@Content(mediaType = APPLICATION_JSON_VALUE, examples = @ExampleObject( name = "404 Response", summary = "Handle exception when resource not found", value = """ { "timestamp": "2023-10-19T06:07:35.321+00:00", "status": 404, "path": "/api/v1/...", "error": "Not Found", "message": "{data} not found" } """ ))}) }) public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setTimestamp(new Date()); errorResponse.setPath(request.getDescription(false).replace("uri=", "")); errorResponse.setStatus(NOT_FOUND.value()); errorResponse.setError(NOT_FOUND.getReasonPhrase()); errorResponse.setMessage(e.getMessage()); return errorResponse; }
– Ném ra ngoại lệ ở UserService
@Service public class UserService { public void deleteUser(int id) { if (id == 1) { throw new ResourceNotFoundException("Not found userId=" + id); } } }
– Thêm API delete user vào UserController để test customize Exception
@DeleteMapping("/{userId}") public ResponseData<?> deleteUser(@PathVariable @Min(value = 1, message = "userId must be greater than 0") int userId) { System.out.println("Request delete userId=" + userId); try { userService.deleteUser(userId); return new ResponseData<>(HttpStatus.NO_CONTENT.value(), "User deleted successfully"); } catch (ResourceNotFoundException e) { return new ResponseData<>(HttpStatus.NOT_FOUND.value(), e.getMessage()); } }
– Test API delete user/{userId} với Postman
curl --location --request DELETE 'http://localhost:8080/user/1' \ --header 'Accept: */*'
⇒ Xem Source Code
⇒ Lấy Source Code
$ git clone https://github.com/luongquoctay87/tayjava-sample-code.git $ git checkout bai-6-handle-exception