SPRING SERIES - LỖI IM LẶNG
@Async Không Async Và Lỗi Mất Tích
PROXY - XANH = TẦNG PHÓNG TO
Đọc từ trái sang phải. Màu đậm dần nghĩa là gần cơ chế hơn. Dấu
* đánh dấu điểm bài viết khai thác.
Triệu chứng A - annotation bị lờ đi
@Async chạy như lời gọi Java bình thường trên thread của caller
Cùng một biên proxyChưa đăng ký async BPP thì không có advice. Có advice rồi, lỗi được định tuyến theo kiểu trả về thay vì ném đồng bộ về caller.
Triệu chứng B - caller không nhận lỗi
void đưa lỗi vào handler; Future giữ failure để consumer quan sát
Mức 1 - từ enable tới proxy
Đăng Ký Async Advisor
Đường mặc định AdviceMode.PROXY
@EnableAsyncImportAsyncConfigurationSelectorvào local application contextselectImports(PROXY)ChọnProxyAsyncConfigurationProxyAsyncConfiguration.asyncAdvisor()Khai bean infrastructure cho async annotationnew AsyncAnnotationBeanPostProcessor()Tạo BPP cùngAsyncAnnotationAdvisorregisterBeanPostProcessors()Đưa async BPP vào chuỗi xử lý beanpostProcessAfterInitialization()Thêm advisor vào proxy sẵn có hoặc tạo proxy cho bean phù hợp
Bất đối xứng của Boot 3.2.5: TransactionAutoConfiguration bật transaction management khi có bean TransactionManager. TaskExecutionAutoConfiguration có thể cấp taskExecutor, nhưng không import @EnableAsync.
Mức 2 - zoom async advice
AsyncExecutionInterceptor.invoke()
Submit task và trả quyền điều khiển
proxy.sendWelcomeEmail()Lời gọi từ ngoài đi vào async advisordetermineAsyncExecutor(userMethod)Chọn qualifier, uniqueTaskExecutor/taskExecutorhoặc local fallback- Tạo
CallableWorker sẽ gọiinvocation.proceed()trên target - Worker bắt
ThrowableChuyển lỗi vàohandleError() doSubmit(task, executor, returnType)Submit task theo kiểu trả về đã khai báo- Proxy trả về caller
nullchovoid; actual async handle choFuture
Kiểm chứng: breakpoint tại invoke(), doSubmit() và log tên thread ở caller/target. Khác thread chứng minh invocation đó đã qua async boundary; cùng thread chưa chỉ ra duy nhất một nguyên nhân.
Mức 3 - zoom handleError
Hai Kênh Nhận Exception
Caller không nhận lại exception theo stack đồng bộ
Method trả void
handleError()Return typevoidkhông thuộc họFutureAsyncUncaughtExceptionHandlerNhận exception, method và argumentsSimpleAsyncUncaughtExceptionHandlerMặc định log ERROR; caller vẫn không có error channel
Method trả Future
handleError()rethrow trong taskNhánhFuture.isAssignableFrom(returnType)- Executor capture failureProxy đã trả actual
FuturehoặcCompletableFuture - Consumer phải quan sát
get(),join(),whenComplete()hoặcexceptionally()
Ưu tiên sửa
- Bật và kiểm chứng async infrastructureThêm
@EnableAsync, gọi qua proxy và xác nhận advice thực sự được chạm. - Kết thúc mọi kênh lỗiCustom handler cho
void; luôn consume hoặc composeFuture.
Khi KHÔNG được kết luận
- Cùng thread nghĩa là chắc chắn thiếu
@EnableAsyncCòn có self-invocation, executor đồng bộ hoặc rejection policy kiểu caller-runs. voidexception mặc định không tạo logSai: default handler log ERROR, nhưng không truyền lỗi về caller.- Có executor là
@Asyncđã bậtExecutor chỉ là nơi chạy task; async BPP/advisor vẫn phải được đăng ký.