0

[Java Backend Zero to Hello] [Phase 5] BÀI 5.12: AOP (ASPECT-ORIENTED PROGRAMMING)

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 5: Spring Framework & Spring Boot 📖 Nội dung: BÀI 5.12: AOP (ASPECT-ORIENTED PROGRAMMING) 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 5.12: AOP (ASPECT-ORIENTED PROGRAMMING)

Mục tiêu

  • Hiểu AOP và các khái niệm
  • Sử dụng Spring AOP
  • Tạo custom Aspect
  • Ứng dụng thực tế

1. AOP LÀ GÌ?

AOP bổ sung các cross-cutting concern (logging, security, transaction) vào code mà không ảnh hưởng business logic.

Vấn đề

public void createUser(User user) {
    log.info("Start createUser");        // Logging
    checkPermission();                     // Security
    userRepository.save(user);             // Business logic
    log.info("End createUser");            // Logging
}

Với AOP

@Loggable
@PreAuthorize("hasRole('ADMIN')")
public void createUser(User user) {
    userRepository.save(user);  // Chỉ business logic
}

2. CÁC KHÁI NIỆM

Thuật ngữ Mô tả
Aspect Module chứa cross-cutting concern
Join Point Điểm có thể chèn advice (method call, exception)
Advice Hành động thực hiện tại join point
Pointcut Biểu thức chọn join point
Introduction Thêm method/field mới
Target Object được áp dụng aspect
Proxy Object được tạo sau khi áp dụng aspect
Weaving Quá trình liên kết aspect với object

3. CÁC LOẠI ADVICE

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        // Trước method
    }

    @After("execution(* com.example.service.*.*(..))")
    public void after(JoinPoint joinPoint) {
        // Sau method (dù thành công hay lỗi)
    }

    @AfterReturning(value = "execution(* com.example.service.*.*(..))",
                    returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        // Sau khi return thành công
    }

    @AfterThrowing(value = "execution(* com.example.service.*.*(..))",
                   throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        // Khi có exception
    }

    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // Trước và sau method
        return joinPoint.proceed();
    }
}

4. POINTCUT EXPRESSION

4.1 execution

// Tất cả method trong package service
@Pointcut("execution(* com.example.service.*.*(..))")

// Method cụ thể
@Pointcut("execution(* com.example.service.UserService.findById(..))")

// Method bắt đầu bằng "find"
@Pointcut("execution(* com.example.service.*.find*(..))")

// Return type bất kỳ, package bất kỳ
@Pointcut("execution(* com.example..*.*(..))")

4.2 within

// Trong class cụ thể
@Pointcut("within(com.example.service.UserService)")

// Trong package
@Pointcut("within(com.example.service..*)")

4.3 @annotation

// Method có annotation cụ thể
@Pointcut("@annotation(com.example.annotation.Loggable)")

4.4 Kết hợp

@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(Loggable)")

5. VÍ DỤ THỰC TẾ

5.1 Logging Aspect

@Aspect
@Component
@Slf4j
public class LoggingAspect {

    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceMethods() {}

    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String method = joinPoint.getSignature().toShortString();
        Object[] args = joinPoint.getArgs();

        log.info("Start {} with args: {}", method, Arrays.toString(args));
        long start = System.currentTimeMillis();

        try {
            Object result = joinPoint.proceed();
            long time = System.currentTimeMillis() - start;
            log.info("End {} in {}ms, result: {}", method, time, result);
            return result;
        } catch (Exception e) {
            log.error("Error in {}: {}", method, e.getMessage());
            throw e;
        }
    }
}

5.2 Custom Annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

5.3 Aspect cho Annotation

@Aspect
@Component
@Slf4j
public class PerformanceAspect {

    @Around("@annotation(LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long time = System.currentTimeMillis() - start;

        log.info("{} executed in {}ms",
            joinPoint.getSignature(), time);
        return result;
    }
}

5.4 Sử dụng

@Service
public class UserService {

    @LogExecutionTime
    public List<User> findAll() {
        return userRepository.findAll();
    }
}

6. SECURITY ASPECT

@Aspect
@Component
public class SecurityAspect {

    @Before("@annotation(requireAuth)")
    public void checkAuth(JoinPoint joinPoint, RequireAuth requireAuth) {
        Authentication auth = SecurityContextHolder.getContext()
            .getAuthentication();

        if (auth == null || !auth.isAuthenticated()) {
            throw new UnauthorizedException("Authentication required");
        }

        String[] requiredRoles = requireAuth.roles();
        for (String role : requiredRoles) {
            if (!hasRole(auth, role)) {
                throw new ForbiddenException("Missing role: " + role);
            }
        }
    }

    private boolean hasRole(Authentication auth, String role) {
        return auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("ROLE_" + role));
    }
}

7. CACHING VỚI AOP

@Aspect
@Component
public class CacheAspect {

    private final CacheManager cacheManager;

    @Around("@annotation(Cacheable)")
    public Object cache(ProceedingJoinPoint joinPoint,
                       Cacheable cacheable) throws Throwable {
        String cacheName = cacheable.value()[0];
        String key = generateKey(joinPoint, cacheable.key());

        Cache cache = cacheManager.getCache(cacheName);
        if (cache != null) {
            Cache.ValueWrapper wrapper = cache.get(key);
            if (wrapper != null) {
                return wrapper.get();
            }
        }

        Object result = joinPoint.proceed();

        if (cache != null && result != null) {
            cache.put(key, result);
        }

        return result;
    }
}

8. TRANSACTION VỚI AOP

@Aspect
@Component
public class TransactionAspect {

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint,
                                   Transactional transactional)
            throws Throwable {
        TransactionStatus status = transactionManager
            .getTransaction(new DefaultTransactionDefinition());

        try {
            Object result = joinPoint.proceed();
            transactionManager.commit(status);
            return result;
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

9. LƯU Ý

9.1 Self-invocation không hoạt động

@Service
public class UserService {

    @LogExecutionTime
    public void methodA() { }

    public void methodB() {
        methodA();  // ❌ AOP không hoạt động (gọi nội bộ)
    }
}

9.2 Proxy

Spring AOP dùng proxy (JDK dynamic proxy hoặc CGLIB). Method private, final không bị intercept.


10. BÀI TẬP THỰC HÀNH

Bài 1: Logging Aspect

Tạo aspect log tất cả method trong service layer.

Bài 2: Performance Monitoring

Tạo annotation @LogExecutionTime và aspect tương ứng.

Bài 3: Security Check

Tạo annotation @RequireRole kiểm tra quyền trước khi vào method.


11. TÓM TẮT

Khái niệm Mô tả
Aspect Module cross-cutting
Advice Hành động tại join point
Pointcut Chọn join point
@Before Trước method
@After Sau method
@Around Bao quanh method
@AfterReturning Sau khi return
@AfterThrowing Khi exception
execution() Pointcut expression

Hoàn thành Phase 5! Tiếp tục với Phase 6: REST API & Best Practices


🧭 Điều hướng bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí