[Java Backend Zero to Hello] BÀI 5.12: AOP (ASPECT-ORIENTED PROGRAMMING)
📚 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