Xử lý lỗi tập trung
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Database mất kết nối, code ném exception mà không ai bắt, client chỉ nhận 500
không rõ lý do. Bọc try/catch vào từng method của controller thì code lặp
lại khắp nơi. Bài này bắt mọi lỗi không lường trước ở một chỗ, còn lỗi cụ thể
xử lý được thì vẫn dùng try/catch như bài Exception.
Khái niệm
🛡️ Xử lý lỗi tập trung: bắt mọi exception chưa được xử lý ở một chỗ duy nhất cho mọi controller, rồi trả về response lỗi thống nhất.
📄 ProblemDetail: class có sẵn của Spring mô tả lỗi theo định dạng JSON chuẩn cho HTTP API, gồm các trường như title, status, detail.
Ví dụ
// AppExceptionHandler.java
package com.shop.api;
import org.slf4j.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.*;
@RestControllerAdvice
class AppExceptionHandler
extends ResponseEntityExceptionHandler {
private static final Logger log =
LoggerFactory.getLogger(
AppExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ProblemDetail handle(Exception ex) {
log.error("Lỗi chưa được xử lý", ex);
ProblemDetail problem =
ProblemDetail.forStatus(500);
problem.setTitle("Đã có lỗi xảy ra");
return problem;
}
}@RestControllerAdviceđánh dấu class chứa phần dùng chung cho mọi controller. Spring tự tìm thấy nó như tìm@RestController.@ExceptionHandler(Exception.class)cho Spring biết gọihandlekhi controller ném exception mà không ai bắt.Exceptionlà cha của mọi exception nên method này bắt tất cả.- Bộ xử lý ghi log đầy đủ exception cho team, còn client chỉ nhận một câu chung chung.
- Method trả
ProblemDetailthì Spring lấy nó làm body, lấystatuscủa nó làm status code của response. - Kế thừa
ResponseEntityExceptionHandlergiữ đúng status cho những lỗi Spring đã biết cách xử lý, như validation sai (400) hay URL không tồn tại (404). Thiếu nó, các lỗi này cũng rơi vàohandlevà thành 500.
Đường đi của một exception chưa ai bắt:
Thử ngay
Giữ các file cũ, thêm AppExceptionHandler.java theo phần Ví dụ và một
controller cố tình ném lỗi:
// TestController.java
package com.shop.api;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/test")
class TestController {
@GetMapping("/boom")
public String boom() {
throw new IllegalStateException(
"Mất kết nối tới kho ở Hà Nội");
}
}Method boom ném exception trước khi kịp trả về chuỗi nào. Chạy server rồi
gọi curl -i http://localhost:5000/api/test/boom.
Đoán trước khi chạy: câu "Mất kết nối tới kho ở Hà Nội" có xuất hiện trong response mà client nhận không?
Xem kết quả
HTTP/1.1 500
Content-Type: application/problem+json
{"instance":"/api/test/boom","status":500,"title":"Đã có lỗi xảy ra"}Không. Câu đó chỉ nằm trong log của server, kèm stack trace đầy đủ. Client
chỉ thấy thông báo chung, không biết gì về cấu trúc bên trong hệ thống.
instance là đường dẫn của request bị lỗi, Spring tự thêm vào.
Lỗi hay gặp
Bọc try/catch vào từng method. Cùng một đoạn xử lý lỗi bị chép lại ở
khắp nơi, sửa một chỗ dễ quên chỗ khác.
// SAI — mọi method đều lặp lại khối này
// OrdersController.java
package com.shop.api;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/orders")
class OrdersController {
@GetMapping("/{id}")
public ResponseEntity<Integer> get(
@PathVariable int id) {
try {
return ResponseEntity.ok(id);
} catch (Exception e) {
return ResponseEntity.status(500).build();
}
}
}// ĐÚNG — lỗi để bộ xử lý tập trung bắt
// OrdersController.java
package com.shop.api;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/orders")
class OrdersController {
@GetMapping("/{id}")
public ResponseEntity<Integer> get(
@PathVariable int id) {
return ResponseEntity.ok(id);
}
}Trả thẳng ex.getMessage() hay stack trace cho client, ví dụ bằng
problem.setDetail(ex.getMessage()). Thông báo lỗi có thể chứa tên bảng,
đường dẫn file, chuỗi kết nối. Chỉ ghi chúng vào log.
Tóm tắt
- Bắt mọi exception chưa xử lý ở một chỗ bằng
@RestControllerAdvicevà@ExceptionHandler. - Kế thừa
ResponseEntityExceptionHandlerđể lỗi 400, 404 của Spring giữ đúng status. - Ghi log đầy đủ cho team, trả
ProblemDetailngắn gọn cho client. - Không bọc
try/catchvào từng method, không lộ chi tiết lỗi ra ngoài.
Tự kiểm tra
0/3 câuMethod handle trong AppExceptionHandler tạo ProblemDetail.forStatus(503) rồi trả về. Một controller ném exception không ai bắt. Client nhận gì?
Thông tin chi tiết của exception (message, stack trace) nên đi đâu?
Method @ExceptionHandler trả về một ProblemDetail có ý nghĩa gì?