Part 03 · Spring Framework & Spring Boot · 3.1.03A

Custom annotation và Spring AOP

Annotation chỉ là metadata. Muốn nó tạo behavior, phải có code đọc metadata bằng compiler, reflection, interceptor, BeanPostProcessor hoặc Spring AOP proxy.


1. AOP bổ sung OOP như thế nào?

Aspect-Oriented Programming tách concern cắt ngang nhiều module như transaction, security, audit, metrics và tracing khỏi business code. OOP chia hệ thống theo object và responsibility; AOP mô tả behavior áp tại nhiều join point. Hai cách bổ sung nhau, nhưng AOP không thay encapsulation hoặc domain design.

Thuật ngữÝ nghĩa
AspectModule chứa cross-cutting concern.
Join pointĐiểm có thể áp behavior; Spring AOP chủ yếu là method execution.
PointcutBiểu thức chọn join points.
AdviceCode chạy before, after hoặc around join point.
WeavingGắn aspect vào target; Spring thường dùng runtime proxy, AspectJ có compile/load-time weaving.

2. Tạo annotation đúng retention và target

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuditedAction {
  String value();
  boolean includeResult() default false;
}

3. Aspect intercept annotation

@Aspect
@Component
class AuditAspect {
  private final AuditSink sink;

  @Around("@annotation(audited)")
  Object audit(ProceedingJoinPoint pjp, AuditedAction audited) throws Throwable {
    long started = System.nanoTime();
    try {
      Object result = pjp.proceed(); // gọi đúng một lần
      sink.success(audited.value(), elapsed(started));
      return result;
    } catch (Throwable error) {
      sink.failure(audited.value(), error, elapsed(started));
      throw error;
    }
  }
}

@Around mạnh nhất nhưng dễ sai nhất: quên proceed(), gọi hai lần, đổi arguments/result hoặc nuốt exception. Audit sink không được log password, token hay PII và không nên làm remote I/O chậm trên request thread.

4. Pointcut và proxy boundary

@AuditedAction("TRANSFER_APPROVE")
public Transfer approve(UUID id) { ... }

public void batchApprove(List<UUID> ids) {
  ids.forEach(this::approve); // self-invocation: aspect không chạy
}

Spring AOP chỉ intercept call đi qua Spring-managed proxy. Public call từ bean khác thường được intercept; self-invocation, private/final method và object tạo bằng new không tạo boundary như mong đợi. Tách annotated operation sang collaborator thay vì self-injection.

5. Meta-annotation và composed annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@AuditedAction("MONEY_MOVEMENT")
public @interface MoneyTransaction {
  @AliasFor(annotation = Transactional.class, attribute = "readOnly")
  boolean readOnly() default false;
}

Composed annotation tạo vocabulary của application nhưng dễ giấu behavior. Chỉ dùng khi semantics ổn định, tên thể hiện intent và team hiểu transaction/audit ordering. Khi tìm annotation trên implementation hoặc proxy, dùng Spring utilities thay vì reflection ngây thơ.

6. Ordering giữa aspects

security → retry → transaction → target → transaction completion → metrics

Thứ tự thực phụ thuộc @Order hoặc Ordered. Retry ngoài transaction tạo transaction mới mỗi attempt; audit success nằm trong transaction có thể được ghi trước khi commit thất bại. Nếu cần sự kiện “đã commit”, dùng transaction synchronization hoặc Outbox. Viết integration test cho call order, exception và rollback.

7. Khi nào không nên dùng AOP?

Dùng tốtKhông nên giấu trong aspect
Metrics/timing, authorization policy, transaction, tracing, stable audit envelope.Business state transition, orchestration, compensation, pricing và flow cần đọc thấy trực tiếp.

Nếu annotation tạo side effect bất ngờ, đổi business result hoặc buộc người đọc nhớ nhiều ordering ngầm, explicit decorator hoặc service thường dễ hiểu và test hơn.

8. Custom validation không nhất thiết dùng AOP

@Constraint(validatedBy = ValidMoneyValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidMoney {
  String message() default "invalid money";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

Custom Bean Validation annotation được Jakarta Validation provider xử lý, không phải Spring AOP. Annotation cũng có thể do compiler, plugin hoặc framework khác xử lý; luôn hỏi “ai đọc annotation và ở phase nào?”.

9. Checklist phỏng vấn và production

Quy tắc thiết kế: custom annotation nên diễn đạt policy ổn định và có owner. Nếu behavior cần đọc theo từng bước để hiểu business outcome, hãy giữ nó explicit trong application service.
Nguồn tham khảo