0

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

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 5: Spring Framework & Spring Boot | Bài 58/86


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 Series

⬅️ Bài trước: BÀI 5.11: CACHING

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: PHASE 6: REST API & BEST PRACTICES - Tổng Quan & Mục Tiêu


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í