Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
AOP (Aspect-Oriented Programming) в Spring
Что такое AOP?
AOP позволяет разделить сквозные задачи (cross-cutting concerns) от основной бизнес-логики. Это техника для "внедрения" дополнительного поведения в методы без изменения самого метода.
Сквозные задачи — это функциональность, которая пронизывает весь код:
- Логирование
- Безопасность и авторизация
- Кэширование
- Управление транзакциями
- Обработка ошибок
- Метрики и мониторинг
Проблема без AOP
// БЕЗ AOP: логирование смешано с бизнес-логикой
public class UserService {
public User getUser(Long id) {
long startTime = System.currentTimeMillis();
System.out.println("[LOG] Getting user with ID: " + id);
try {
// Реальная логика
User user = userRepository.findById(id);
long duration = System.currentTimeMillis() - startTime;
System.out.println("[LOG] User fetched in " + duration + "ms");
return user;
} catch (Exception e) {
System.err.println("[ERROR] Failed to get user: " + e.getMessage());
throw e;
}
}
public User createUser(String email, String password) {
long startTime = System.currentTimeMillis();
System.out.println("[LOG] Creating user: " + email);
try {
// Реальная логика
User user = new User(email, password);
userRepository.save(user);
long duration = System.currentTimeMillis() - startTime;
System.out.println("[LOG] User created in " + duration + "ms");
return user;
} catch (Exception e) {
System.err.println("[ERROR] Failed to create user: " + e.getMessage());
throw e;
}
}
public void deleteUser(Long id) {
long startTime = System.currentTimeMillis();
System.out.println("[LOG] Deleting user with ID: " + id);
try {
// Реальная логика
userRepository.deleteById(id);
long duration = System.currentTimeMillis() - startTime;
System.out.println("[LOG] User deleted in " + duration + "ms");
} catch (Exception e) {
System.err.println("[ERROR] Failed to delete user: " + e.getMessage());
throw e;
}
}
}
// Проблемы:
// 1. Логирование и таймер повторяется в каждом методе (нарушение DRY)
// 2. Бизнес-логика смешана с техническими задачами
// 3. Сложно менять логику логирования (нужно менять каждый метод)
// 4. Код становится нечитаемым и тяжелым
Решение с AOP
// 1. Определяем Aspect
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// Pointcut: на какие методы применять это поведение?
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// Before: выполнить перед методом
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.info("Calling method: {} with args: {}", methodName, args);
}
// After: выполнить после метода (в любом случае)
@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
logger.info("Method {} completed", methodName);
}
// AfterReturning: выполнить после успешного возврата
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
logger.info("Method {} returned: {}", methodName, result);
}
// AfterThrowing: выполнить если был exception
@AfterThrowing(pointcut = "serviceLayer()", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
logger.error("Method {} threw exception: {}", methodName, exception.getMessage());
}
// Around: полный контроль, выполнить до и после
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
logger.info("START: {}", methodName);
try {
Object result = joinPoint.proceed(); // Выполняем реальный метод
long duration = System.currentTimeMillis() - startTime;
logger.info("SUCCESS: {} in {}ms", methodName, duration);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
logger.error("FAILED: {} in {}ms with error: {}", methodName, duration, e.getMessage());
throw e;
}
}
}
// 2. Теперь UserService чистый:
public class UserService {
public User getUser(Long id) {
return userRepository.findById(id); // Чистая логика!
}
public User createUser(String email, String password) {
User user = new User(email, password);
return userRepository.save(user); // Чистая логика!
}
public void deleteUser(Long id) {
userRepository.deleteById(id); // Чистая логика!
}
}
Примеры использования AOP
Пример 1: Кэширование
@Aspect
@Component
public class CachingAspect {
private final Map<String, Object> cache = new HashMap<>();
@Around("@annotation(com.example.annotations.Cacheable)")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().getName() +
Arrays.toString(joinPoint.getArgs());
if (cache.containsKey(key)) {
System.out.println("[CACHE HIT] " + key);
return cache.get(key);
}
Object result = joinPoint.proceed();
cache.put(key, result);
System.out.println("[CACHE MISS] " + key);
return result;
}
}
// Использование
public class UserService {
@Cacheable // Этот метод будет кэширован
public User getUser(Long id) {
return userRepository.findById(id);
}
}
Пример 2: Безопасность (проверка прав доступа)
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(com.example.annotations.RequireAdmin)")
public void checkAdminRole(JoinPoint joinPoint) {
// Получаем текущего пользователя из контекста
User currentUser = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!currentUser.hasRole("ADMIN")) {
throw new AccessDeniedException("Admin role required");
}
}
}
// Использование
public class AdminService {
@RequireAdmin // Этот метод доступен только админам
public void deleteAllUsers() {
userRepository.deleteAll();
}
}
Пример 3: Обработка исключений
@Aspect
@Component
public class ExceptionHandlingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void handleException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
if (ex instanceof DatabaseException) {
// Логируем как критическую ошибку
logger.error("Database error in {}: {}", methodName, ex.getMessage());
// Отправляем алерт
alertService.sendAlert("DB Error: " + methodName);
} else if (ex instanceof ValidationException) {
// Логируем как warning
logger.warn("Validation error in {}: {}", methodName, ex.getMessage());
}
}
}
Пример 4: Метрики
@Aspect
@Component
public class MetricsAspect {
private final MeterRegistry meterRegistry;
@Around("execution(* com.example.service.*.*(..)")
public Object recordMetrics(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
meterRegistry.timer("method.duration", "method", methodName)
.record(duration, TimeUnit.MILLISECONDS);
meterRegistry.counter("method.success", "method", methodName).increment();
return result;
} catch (Exception e) {
meterRegistry.counter("method.error", "method", methodName).increment();
throw e;
}
}
}
Pointcut выражения
// execution: по типу выполнения
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
// По названию метода
@Pointcut("execution(* get*(..))")
public void allGetMethods() {}
// По аннотации
@Pointcut("@annotation(com.example.annotations.Loggable)")
public void loggableMethods() {}
// По типу возвращаемого значения
@Pointcut("execution(String com.example..*(..))")
public void methodsReturningString() {}
// Комбинирование
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* *.get*(..)")
public void serviceMethodsExceptGetters() {}
Выводы
- AOP используется для разделения сквозных задач от бизнес-логики
- Основные типы advice:
- @Before — выполнить перед методом
- @After — выполнить после метода
- @AfterReturning — после успешного возврата
- @AfterThrowing — при исключении
- @Around — полный контроль
- Практическое применение:
- Логирование
- Кэширование
- Безопасность
- Транзакции
- Метрики
- Преимущества:
- Код чище и понятнее
- DRY принцип
- Легче менять техническое поведение
- Меньше дублирования