99 Ngày Spring — Ngày 62: Custom annotation kết hợp AOP
Ngày 61 chọn phương thức bằng tên class. Khi chỉ một vài method cần đo, annotation tự tạo giúp đặt ý định ngay cạnh nghiệp vụ. Spring AOP đọc metadata đó qua proxy và chạy advice. Ví dụ dùng Spring Boot 3.x, Java 21 và spring-boot-starter-aop.

1. Khai báo annotation để runtime đọc được
@Target(METHOD) giới hạn vị trí gắn; @Retention(RUNTIME) cho phép Spring nhìn thấy annotation khi ứng dụng chạy. Thuộc tính value đặt tên tác vụ trong log, không kéo toàn bộ tham số nhạy cảm vào log.
// Timed.java — package vn.motdev.day62
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed { String value(); }2. Bind annotation trong pointcut
Biểu thức @annotation(timed) chỉ chọn method có annotation và truyền chính instance annotation vào advice. @Around cho phép đo toàn bộ thời gian. finally luôn chạy, nhưng proceed() chỉ gọi đúng một lần để không thay đổi nghiệp vụ. Đừng nuốt lỗi hoặc thay giá trị trả về.
// TimingAspect.java — @Aspect and @Component
@Around(value = "@annotation(timed)", argNames = "call,timed")
public Object measure(ProceedingJoinPoint call, Timed timed)
throws Throwable {
long start = System.nanoTime();
try { return call.proceed(); }
finally {
log.info("operation={} elapsedNs={}", timed.value(),
System.nanoTime() - start);
}
}3. Gắn lên Spring bean và gọi qua proxy
Aspect phải là Spring bean; @Aspect đơn lẻ không đăng ký nó. Với Boot 3.x, starter AOP cung cấp các thành phần cần thiết và Boot tự cấu hình khi điều kiện thỏa. Gọi từ bean khác qua injection. this.checkout() trong cùng đối tượng đi thẳng vào target nên bỏ qua advice, như bẫy self-invocation ở ngày 61.
// CheckoutService.java
@Service
public class CheckoutService {
@Timed("checkout")
public String checkout(String orderId) {
if (orderId.isBlank()) throw new IllegalArgumentException();
return "accepted:" + orderId;
}
}4. Kiểm chứng hợp đồng
Test qua bean lấy từ Spring context: gọi hợp lệ và lỗi, xác nhận đúng một log timing cho mỗi lời gọi, kết quả vẫn như cũ và ngoại lệ vẫn truyền ra. Test bằng new TimingAspect() chỉ kiểm tra một method thường, không chứng minh pointcut và proxy đã được nối. Đo latency thật nên dùng Micrometer/Actuator thay vì tự gom log thủ công.
Bài tập: gắn @Timed cho hai method, giữ một method không gắn để kiểm tra pointcut.
Bài tập: gọi nội bộ bằng this, quan sát thiếu log, rồi tách method sang bean khác.
Annotation mô tả nơi áp dụng; aspect giữ xử lý dùng chung. Cặp này hữu ích khi quy tắc rõ và hẹp. Ngày 63 chuyển sang application event để tách phản ứng sau một hành động nghiệp vụ.
Tài liệu đối chiếu
https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/advice.html
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


