Giới Thiệu AOP
AOP (Lập trình hướng khía cạnh) là phương pháp lập trình tập trung vào việc bổ sung chức năng cho các phương thức cụ thể mà không thay đổi mã nguồn gốc. Khi cần thống kê thời gian thực thi các phương thức nghiệp vụ, thay vì sửa đổi từng phương thức, AOP cho phép định nghĩa logic chung trong một mẫu duy nhất áp dụng cho nhiều phương thức đích.
Ưu điểm:
- Giảm mã trùng lặp
- Dễ bảo trì
- Tách biệt mối quan tâm
- Không xâm phạm mã nguồn gốc
Thực Hành Cơ Bản
Yêu cầu: Đo thời gian thực thi phương thức tầng service
Cài đặt:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Lớp Aspect:
@Component
@Aspect
public class PerformanceMonitor {
@Around("execution(* com.example.service.*.*(..))")
public Object measureDuration(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
System.out.println(pjp.getSignature() + " thực thi trong: " + (endTime - startTime) + "ms");
return result;
}
}
Thành Phần Chính
- JoinPoint: Điểm thực thi phương thức có thể chặn
- Advice: Logic chung được áp dụng (ví dụ: đo thời gian)
- Pointcut: Biểu thức xác định phương thức đích
- Aspect: Kết hợp Pointcut và Advice
- Target Object: Đối tượng chứa phương thức gốc
Loại Advice
@Aspect
public class CustomAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeExecution(JoinPoint jp) {
System.out.println("Thực thi trước phương thức");
}
@Around("customPointcut()")
public Object aroundExecution(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Bắt đầu bao quanh");
Object result = pjp.proceed();
System.out.println("Kết thúc bao quanh");
return result;
}
@Pointcut("execution(* com.example.service.*.*(..))")
private void customPointcut() {}
}
Thứ Tự Thực Thi Advice
Khi nhiều Aspect cùng áp dụng:
- Mặc định: Sắp xếp theo tên lớp
- Kiểm soát bằng @Order:
@Aspect
@Order(1)
public class PrimaryAspect {
@Before("execution(* com.example.service.*.*(..))")
public void primaryAdvice() {
System.out.println("Thực thi đầu tiên");
}
}
Biểu Thức Pointcut
execution:
@Before("execution(public * com.example..*.update*(..))")
@annotation:
@Target(ElementType.METHOD)
public @interface AuditLog {}
@Aspect
public class AuditAspect {
@After("@annotation(com.example.AuditLog)")
public void auditAction() {
System.out.println("Ghi nhật ký kiểm toán");
}
}
Truy Xuất Thông Tin JoinPoint
@Aspect
public class InfoAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logMethodInfo(ProceedingJoinPoint pjp) throws Throwable {
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
System.out.println("Gọi phương thức: " + className + "." + methodName);
return pjp.proceed();
}
}
Ví Dụ Thực Tế: Ghi Nhật Ký Hoạt Động
Bước triển khai:
- Tạo annotation @Traceable
- Đánh dấu phương thức cần ghi nhật ký
- Xử lý trong Aspect:
@Aspect
@Component
public class ActivityLogger {
@Around("@annotation(com.example.Traceable)")
public Object logActivity(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long duration = System.currentTimeMillis() - start;
ActivityLog log = new ActivityLog();
log.setMethodName(pjp.getSignature().getName());
log.setParameters(Arrays.toString(pjp.getArgs()));
log.setDuration(duration);
logRepository.save(log);
return result;
}
}