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

Как связаны Self-Injection и Spring AOP?

2.0 Middle🔥 131 комментариев
#ORM и Hibernate

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

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

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

Как связаны Self-Injection и Spring AOP

Self-Injection и Spring AOP — это две продвинутые техники Spring, которые часто используются вместе для решения нетривиальных архитектурных проблем. Давайте разберём их связь и практическое применение.

1. Что такое Self-Injection (Самовнедрение)

Self-Injection — это техника, когда Bean внедряет сам себя через свой интерфейс.

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class UserService {
  // Self-Injection: сервис внедряет сам себя
  @Autowired
  private UserService self;
  
  public void createUser(String name) {
    // Прямой вызов методов
    validateUser(name);
    saveUser(name);
  }
  
  public void createUserWithTransaction(String name) {
    // Вызов через self гарантирует, что перехватчики сработают
    self.createUser(name);
  }
  
  private void validateUser(String name) {
    System.out.println("Validating: " + name);
  }
  
  private void saveUser(String name) {
    System.out.println("Saving: " + name);
  }
}

Почему это работает?

  • Spring создаёт CGLIB прокси для UserService
  • Прокси перехватывает все вызовы к методам
  • Когда вызываем self.createUser(), вызываем через прокси
  • Через прокси применяются все перехватчики (AOP advice)

2. Проблема, которую решает Self-Injection

Без Self-Injection — AOP не работает:

@Service
public class OrderService {
  @Transactional // Аннотация AOP
  public void createOrder(Order order) {
    saveOrder(order);
    notifyCustomer(order);
  }
  
  public void createOrderIfNotExists(Order order) {
    if (!exists(order)) {
      createOrder(order); // Прямой вызов! @Transactional НЕ сработает
    }
  }
  
  private void saveOrder(Order order) {}
  private void notifyCustomer(Order order) {}
  private boolean exists(Order order) { return false; }
}

// Проблема: createOrderIfNotExists вызывает createOrder напрямую
// Это вызывает реальный объект, НЕ прокси
// @Transactional не работает

С Self-Injection — AOP работает:

@Service
public class OrderService {
  @Autowired
  private OrderService self; // Self-Injection!
  
  @Transactional
  public void createOrder(Order order) {
    saveOrder(order);
    notifyCustomer(order);
  }
  
  public void createOrderIfNotExists(Order order) {
    if (!exists(order)) {
      self.createOrder(order); // Вызов через прокси! @Transactional сработает
    }
  }
  
  private void saveOrder(Order order) {}
  private void notifyCustomer(Order order) {}
  private boolean exists(Order order) { return false; }
}

3. Spring AOP: Как работают перехватчики

// AOP аннотации создают перехватчики

@Service
public class PaymentService {
  @Transactional // Транзакция
  public void processPayment(Payment payment) {
    // Код выполняется в транзакции
  }
  
  @Async // Асинхронное выполнение
  public void sendNotification(String message) {
    // Выполняется в отдельном потоке
  }
  
  @Cached("payments") // Кэширование
  public Payment getPayment(Long id) {
    // Результат кэшируется
    return null;
  }
}

// Spring создаёт CGLIB прокси:
public class PaymentServiceProxy extends PaymentService {
  private TransactionAdvice transactionAdvice;
  private AsyncAdvice asyncAdvice;
  private CacheAdvice cacheAdvice;
  
  @Override
  public void processPayment(Payment payment) {
    // 1. Начало транзакции
    transactionAdvice.beforeTransaction();
    try {
      // 2. Вызов реального объекта
      super.processPayment(payment);
      // 3. Коммит
      transactionAdvice.afterTransaction();
    } catch (Exception e) {
      // 4. Rollback
      transactionAdvice.onException();
    }
  }
}

4. Практический пример: Self-Injection + @Transactional

@Service
public class UserService {
  @Autowired
  private UserService self; // Self-Injection
  
  @Autowired
  private UserRepository userRepository;
  
  // Публичный метод — точка входа
  public void bulkCreateUsers(List<String> names) {
    for (String name : names) {
      // Каждый пользователь создаётся в отдельной транзакции
      self.createUserInTransaction(name);
    }
  }
  
  // Эта транзакция применяется благодаря self-injection
  @Transactional
  public void createUserInTransaction(String name) {
    try {
      User user = new User(name);
      userRepository.save(user);
    } catch (DataIntegrityViolationException e) {
      // Транзакция rollback автоматически
      System.out.println("Failed to create user: " + name);
    }
  }
}

// Без self-injection:
public void bulkCreateUsers_WRONG(List<String> names) {
  for (String name : names) {
    // Все в одной транзакции, если есть ошибка — все откатываются
    createUserInTransaction(name); // НЕПРАВИЛЬНО!
  }
}

5. Self-Injection + Custom AOP

// Кастомная AOP аннотация
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimited {
  int maxRequests() default 10;
  int windowSeconds() default 60;
}

// AOP Aspect
@Aspect
@Component
public class RateLimitingAspect {
  private final RateLimiter rateLimiter = new RateLimiter();
  
  @Before("@annotation(RateLimited)")
  public void checkRateLimit(JoinPoint joinPoint) throws Throwable {
    if (!rateLimiter.allowRequest()) {
      throw new TooManyRequestsException();
    }
  }
}

// Использование с self-injection
@Service
public class ApiService {
  @Autowired
  private ApiService self;
  
  public void handleRequest(Request request) {
    // Вызываем через прокси для применения rate limiting
    self.processRequest(request);
  }
  
  @RateLimited(maxRequests = 100)
  public void processRequest(Request request) {
    System.out.println("Processing: " + request);
  }
}

6. Self-Injection + @Async

@Service
public class EmailService {
  @Autowired
  private EmailService self;
  
  @Autowired
  private EmailRepository emailRepository;
  
  // Синхронный публичный метод
  public void sendBulkEmails(List<String> recipients) {
    for (String recipient : recipients) {
      // Асинхронная отправка каждого письма
      self.sendEmailAsync(recipient);
    }
  }
  
  // Асинхронный приватный метод
  @Async
  public void sendEmailAsync(String recipient) {
    // Выполняется в отдельном потоке
    try {
      Thread.sleep(1000); // Имитация отправки
      System.out.println("Email sent to " + recipient);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}

// Конфигурация
@Configuration
@EnableAsync
public class AsyncConfig {
  @Bean(name = "taskExecutor")
  public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("AsyncThread-");
    executor.initialize();
    return executor;
  }
}

7. Когда НЕ использовать Self-Injection

// ❌ НЕ используй, если есть альтернатива
@Service
public class BadExample {
  @Autowired
  private BadExample self; // Плохой паттерн
  
  public void methodA() {
    self.methodB(); // Почему не просто methodB()?
  }
  
  public void methodB() {}
}

// ✅ Лучше: использовать другой класс
@Service
public class GoodExample {
  @Autowired
  private HelperService helper;
  
  public void methodA() {
    helper.doSomething();
  }
}

8. Альтернатива: ObjectProvider

import org.springframework.beans.factory.ObjectProvider;

@Service
public class UserService {
  @Autowired
  private ObjectProvider<UserService> userServiceProvider;
  
  public void bulkCreateUsers(List<String> names) {
    for (String name : names) {
      // Получаем прокси каждый раз
      userServiceProvider.getObject().createUserInTransaction(name);
    }
  }
  
  @Transactional
  public void createUserInTransaction(String name) {
    System.out.println("Creating: " + name);
  }
}

9. Диагностика проблем с AOP

// Если AOP не работает, проверь:

@Service
public class DiagnosticService {
  @Autowired
  private ApplicationContext context;
  
  @PostConstruct
  public void diagnose() {
    // 1. Проверить, что это прокси
    System.out.println("Class: " + getClass()); // Должно быть $Proxy
    
    // 2. Проверить Bean в контексте
    Object bean = context.getBean("diagnosticService");
    System.out.println("Bean class: " + bean.getClass());
    
    // 3. Проверить, совпадают ли
    System.out.println("Is proxy: " + (bean instanceof DiagnosticService));
  }
}

В итоге: Self-Injection и Spring AOP неразрывно связаны. Self-Injection позволяет вызывать методы через прокси, благодаря чему AOP перехватчики работают корректно. Это особенно важно для @Transactional, @Async, кэширования и других декларативных функций Spring. Используй эту технику осторожно и только когда действительно нужно применить AOP к методу из того же класса.