← Назад к вопросам

Какие знаешь способы реализации аспектно-ориентированного подхода?

2.0 Middle🔥 131 комментариев
#SOLID и паттерны проектирования#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Аспектно-ориентированное программирование (AOP)

AOP (Aspect-Oriented Programming) — парадигма, которая позволяет отделить cross-cutting concerns (логирование, безопасность, кэширование, транзакции) от основной бизнес-логики.

1. Spring AOP (самый распространённый подход)

Spring использует Dynamic Proxies под капотом для создания аспектов.

Аннотация @Aspect

@Aspect
@Component
public class LoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
    // Логирует все методы в сервисах
    @Before("execution(* com.example.service.*.*(..))") 
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("Method invoked: {}", methodName);
    }
    
    // Логирует результат
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", 
                    returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("Method {} returned: {}", methodName, result);
    }
    
    // Логирует исключения
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", 
                   throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        logger.error("Method {} threw exception: {}", methodName, ex.getMessage());
    }
}

Pointcut выражения

// ✅ Все методы в сервисах
@Pointcut("execution(* com.example.service.*.*(..))") 
public void serviceMethods() {}

// ✅ Все методы аннотированные @Transactional
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)") 
public void transactionalMethods() {}

// ✅ Все публичные методы с аргументами User
@Pointcut("execution(public * *(com.example.domain.User, ..))") 
public void userRelatedMethods() {}

// ✅ Сочетание условий
@Pointcut("execution(public * com.example.service.*.*(..)) && @annotation(com.example.annotations.Cacheable)")
public void cacheableMethods() {}

Практический пример: Performance Monitoring

@Aspect
@Component
public class PerformanceAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);
    
    @Around("execution(public * com.example.service.*.*(..))") 
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            // Выполни исходный метод
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;
            
            String methodName = joinPoint.getSignature().getName();
            if (duration > 1000) {
                logger.warn("Method {} took {}ms (slow)", methodName, duration);
            } else {
                logger.info("Method {} took {}ms", methodName, duration);
            }
        }
    }
}

2. Spring @Transactional (встроенный AOP)

@Service
public class PaymentService {
    
    // Spring автоматически оборачивает в транзакцию
    @Transactional
    public void transferMoney(Account from, Account to, BigDecimal amount) {
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        accountRepository.save(from);
        accountRepository.save(to);
        // COMMIT автоматически
    }
    
    // Readonly транзакция
    @Transactional(readOnly = true)
    public List<Account> getAllAccounts() {
        return accountRepository.findAll();
    }
}

3. Spring @Cacheable / @CacheEvict (встроенный AOP)

@Service
public class UserService {
    
    // Автоматическое кэширование результата
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
    
    // Инвалидация кэша
    @CacheEvict(value = "users", key = "#id")
    public void updateUser(Long id, User updated) {
        User user = userRepository.findById(id).orElseThrow();
        user.setName(updated.getName());
        userRepository.save(user);
    }
    
    // Очистка всего кэша
    @CacheEvict(value = "users", allEntries = true)
    public void clearAllCache() {}
}

4. Spring Security @PreAuthorize / @PostAuthorize (AOP для безопасности)

@Service
public class DocumentService {
    
    // Проверка прав до выполнения метода
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteDocument(Long id) {
        documentRepository.deleteById(id);
    }
    
    // Проверка прав на основе параметров
    @PreAuthorize("@securityService.canModifyUser(#userId)")
    public void updateUser(Long userId, User updated) {
        userRepository.save(updated);
    }
    
    // Проверка прав на основе результата
    @PostAuthorize("returnObject.owner == authentication.principal.id")
    public Document getDocument(Long id) {
        return documentRepository.findById(id).orElseThrow();
    }
}

5. Кастомные аннотации с AOP

// Определи аннотацию
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
    int attempts() default 3;
    long delayMs() default 100;
}

// Реализуй аспект
@Aspect
@Component
public class RetryAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(RetryAspect.class);
    
    @Around("@annotation(com.example.annotations.Retry)")
    public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
        Retry retry = getAnnotation(joinPoint, Retry.class);
        
        for (int i = 0; i < retry.attempts(); i++) {
            try {
                return joinPoint.proceed();
            } catch (Exception e) {
                if (i == retry.attempts() - 1) {
                    throw e;
                }
                logger.warn("Retry attempt {} of {}", i + 1, retry.attempts());
                Thread.sleep(retry.delayMs());
            }
        }
        return null;
    }
}

// Использование
@Service
public class ExternalApiService {
    
    @Retry(attempts = 3, delayMs = 500)
    public String callExternalApi() {
        // Если выбросит исключение, повторит 3 раза
        return restTemplate.getForObject("http://api.example.com/data", String.class);
    }
}

6. AspectJ (более мощный, но сложнее)

AspectJ — это отдельный язык для AOP, поддерживает compile-time weaving и load-time weaving.

// AspectJ синтаксис
public aspect LoggingAspect {
    
    // Pointcut
    pointcut publicMethods() : 
        execution(public * com.example.service.*.*(..));
    
    // Advice (до выполнения)
    before() : publicMethods() {
        System.out.println("Method called: " + thisJoinPoint.getSignature());
    }
    
    // Advice (после выполнения)
    after() returning : publicMethods() {
        System.out.println("Method completed");
    }
}

7. Декоратор паттерн (альтернатива AOP)

// Интерфейс
public interface UserService {
    User getUser(Long id);
}

// Реализация
@Service
public class UserServiceImpl implements UserService {
    public User getUser(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
}

// Декоратор для логирования
@Component
public class LoggingUserService implements UserService {
    
    private final UserService delegate;
    private final Logger logger = LoggerFactory.getLogger(LoggingUserService.class);
    
    public LoggingUserService(UserService delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public User getUser(Long id) {
        logger.info("Getting user with id: {}", id);
        User user = delegate.getUser(id);
        logger.info("Got user: {}", user);
        return user;
    }
}

8. Динамические прокси (низкоуровневый подход)

public class ProxyFactory {
    
    public static <T> T createProxy(T target, Class<T> interfaceClass) {
        return (T) Proxy.newProxyInstance(
            interfaceClass.getClassLoader(),
            new Class[]{interfaceClass},
            (proxy, method, args) -> {
                // Логирование до
                System.out.println("Before: " + method.getName());
                
                // Выполнение
                Object result = method.invoke(target, args);
                
                // Логирование после
                System.out.println("After: " + method.getName());
                
                return result;
            }
        );
    }
}

// Использование
UserService service = ProxyFactory.createProxy(
    new UserServiceImpl(), 
    UserService.class
);

9. Comparator пример: разные способы добавить логирование

// ❌ Без AOP: логирование в коде
public void createUser(User user) {
    logger.info("Creating user: {}", user);
    userRepository.save(user);
    logger.info("User created");
}

// ✅ С AOP: логирование отдельно
@Service
public class UserService {
    public void createUser(User user) {
        userRepository.save(user);
    }
}

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.UserService.createUser(..))")
    public void logBefore(JoinPoint joinPoint) {
        User user = (User) joinPoint.getArgs()[0];
        logger.info("Creating user: {}", user);
    }
}

Сравнение подходов

ПодходСложностьПроизводительностьГибкостьКогда использовать
Spring AOPСредняяХорошаяСредняяБольшинство случаев
AspectJВысокаяОтличнаяВысокаяСложные требования
ДекораторПростаяОтличнаяСредняяСпецифичные нужды
Динамические проксиСредняяНормальнаяВысокаяНизкоуровневые нужды

Выводы

  1. Spring AOP — самый практичный выбор для Java приложений
  2. @Aspect и @Pointcut — основные инструменты
  3. @Transactional, @Cacheable, @PreAuthorize — встроенные аспекты Spring
  4. Кастомные аннотации — позволяют создавать повторно используемые аспекты
  5. Не переусложняй — AOP усложняет отладку, используй когда действительно нужно
  6. AspectJ мощнее Spring AOP, но требует additional configuration