Xử lý lỗi tập trung
Chuẩn hoá lỗi cho ứng dụng web: mọi exception được chuyển thành JSON thống nhất (code, message, path, errors), mapping sẵn cho lỗi validation và lỗi bảo mật, kèm điểm mở rộng để thêm handler/customizer theo nghiệp vụ.
Khi nào sử dụng
- Service cần format lỗi trả về client thống nhất giữa các API.
- Muốn giảm boilerplate
try/catch/@ExceptionHandlerthủ công trong từng controller. - Cần mapping lỗi validation (
@Valid,@NotBlank,@Size...) và lỗi bảo mật (401/403) sang mã lỗi nghiệp vụ. - Cần tuỳ chỉnh log lỗi theo HTTP status, ẩn message nhạy cảm, hoặc bổ sung field vào response lỗi (traceId...).
Cài đặt
Thêm dependency vào pom.xml, không cần khai báo version vì BOM đã quản lý:
<dependency>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-error</artifactId>
</dependency>
Hướng dẫn khai báo parent/BOM và Maven repository xem tại Cài đặt.
Cấu hình
Dependency có hai auto-configuration độc lập: servlet dùng prefix govex.error.handling.*, reactive dùng prefix govex.error.*. Cả hai đều matchIfMissing = true nên mặc định bật ngay khi thêm dependency.
| Property | Mô tả | Mặc định |
|---|---|---|
govex.error.handling.enabled | Bật/tắt xử lý lỗi tập trung cho ứng dụng servlet | true |
govex.error.enabled | Bật/tắt xử lý lỗi tập trung cho ứng dụng WebFlux | true |
govex.error.handling.add-path-to-error | Thêm đường dẫn request vào thông tin lỗi trả về | true |
govex.error.handling.search-super-class-hierarchy | Tìm mapping theo super class khi không có mapping trực tiếp | false |
govex.error.handling.http-status-in-json-response | Ghi HTTP status vào JSON response lỗi | false |
govex.error.handling.exception-logging | Mức ghi log exception: NO_LOGGING, MESSAGE_ONLY, WITH_STACKTRACE | WITH_STACKTRACE |
govex.error.handling.full-stacktrace-classes | Danh sách class exception luôn ghi log kèm stacktrace đầy đủ | — |
govex.error.handling.full-stacktrace-http-statuses | Danh sách HTTP status ghi log kèm stacktrace đầy đủ | 500 |
govex.error.handling.hide-message-http-statuses | Danh sách HTTP status ẩn message lỗi khỏi response | — |
govex.error.handling.log-levels | Mức log theo HTTP status, hỗ trợ wildcard dạng 40x, 4xx | — |
govex.error.handling.http-statuses | Mapping exception class sang HTTP status | — |
govex.error.handling.codes | Mapping exception class sang mã lỗi | Bộ defaults có sẵn |
govex.error.handling.messages | Mapping exception class sang thông điệp | Bộ defaults có sẵn |
govex.error.handling.json-field-names.code | Tên field JSON chứa mã lỗi | code |
govex.error.handling.json-field-names.message | Tên field JSON chứa thông điệp lỗi | message |
govex.error.handling.json-field-names.errors | Tên field JSON chứa danh sách lỗi chi tiết | errors |
Dependency nạp sẵn file defaults cho lỗi validation/bảo mật (ví dụ lỗi @NotNull có mã 00400015, lỗi validate chung 00400000); giá trị khai báo trong application.yml ghi đè defaults.
Sử dụng
Ví dụ cấu hình
govex:
error:
handling:
enabled: true
add-path-to-error: true
exception-logging: WITH_STACKTRACE
hide-message-http-statuses:
- "500"
log-levels:
"500": ERROR
"4xx": WARN
Ném lỗi nghiệp vụ
Dùng ServiceException kèm ResultCode để trả đúng mã lỗi/thông điệp:
throw new ServiceException(ResultCode.of("I0010001", "Không tìm thấy hồ sơ"));
Hoặc gắn mã lỗi cố định cho một class exception:
@ResponseErrorCode("I0010001")
public class InvoiceNotFoundException extends ServiceException {
public InvoiceNotFoundException(String invoiceNo) {
super("Không tìm thấy hoá đơn " + invoiceNo);
}
}
Thêm handler cho exception riêng
Khai báo bean ResponseExceptionHandler — các handler được duyệt theo thứ tự, handler đầu tiên canHandle được exception sẽ được dùng:
@Bean
public ResponseExceptionHandler myBusinessExceptionHandler() {
return new ResponseExceptionHandler() {
@Override
public boolean canHandle(Throwable exception) {
return exception instanceof MyBusinessException;
}
@Override
public ErrorResponse handle(Throwable exception) {
return new ErrorResponse(HttpStatus.BAD_REQUEST, "MY_CODE", exception.getMessage());
}
};
}
Bổ sung field vào response lỗi
@Bean
public ErrorResponseCustomizer traceIdCustomizer() {
return response -> response.addErrorProperty("traceId", currentTraceId());
}
Lưu ý
- Cấu hình servlet và reactive tách biệt: chỉ nhóm property đúng với loại ứng dụng mới có hiệu lực.
- Các handler/customizer mặc định đều có
@ConditionalOnMissingBean— khai báo bean cùng loại để ghi đè. - Khi
server.error.include-message/include-binding-errorscủa Spring Boot không bật, danh sách lỗi chi tiết có thể bị lược bỏ hoặc thay bằng thông báo chung ở một số HTTP status. - Lỗi validation và lỗi Spring Security chỉ được map sẵn khi class tương ứng (
spring-boot-starter-validation,spring-boot-starter-security) có trên classpath. - Nên khai báo
hide-message-http-statusescho các status5xxđể không lộ chi tiết kỹ thuật ra ngoài.