← Назад к вопросам
Какие знаешь способы реализации аспектно-ориентированного подхода?
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 | Высокая | Отличная | Высокая | Сложные требования |
| Декоратор | Простая | Отличная | Средняя | Специфичные нужды |
| Динамические прокси | Средняя | Нормальная | Высокая | Низкоуровневые нужды |
Выводы
- Spring AOP — самый практичный выбор для Java приложений
- @Aspect и @Pointcut — основные инструменты
- @Transactional, @Cacheable, @PreAuthorize — встроенные аспекты Spring
- Кастомные аннотации — позволяют создавать повторно используемые аспекты
- Не переусложняй — AOP усложняет отладку, используй когда действительно нужно
- AspectJ мощнее Spring AOP, но требует additional configuration