VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Spring Boot Web APIHoàn thiện API
Bài 17/19
5 phút

Xử lý lỗi tập trung

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 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ọi handle khi controller ném exception mà không ai bắt. Exception là 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ả ProblemDetail thì Spring lấy nó làm body, lấy status của nó làm status code của response.
  • Kế thừa ResponseEntityExceptionHandler giữ đú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ào handle và thành 500.

Đường đi của một exception chưa ai bắt:

Controller ném exception Spring tìm @ExceptionHandler AppExceptionHandler.handle Log của server: message + stack trace Client: 500 + ProblemDetail
Exception đi tới bộ xử lý lỗi tập trung

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 @RestControllerAdvice và @ExceptionHandler.
  • Kế thừa ResponseEntityExceptionHandler để lỗi 400, 404 của Spring giữ đúng status.
  • Ghi log đầy đủ cho team, trả ProblemDetail ngắn gọn cho client.
  • Không bọc try/catch vào từng method, không lộ chi tiết lỗi ra ngoài.

Tự kiểm tra

0/3 câu
Câu 1

Method 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ì?

Câu 2

Thông tin chi tiết của exception (message, stack trace) nên đi đâu?

Câu 3

Method @ExceptionHandler trả về một ProblemDetail có ý nghĩa gì?

Quan hệ một-nhiềuXác thực bằng JWT

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt