Как связаны Self-Injection и Spring AOP?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как связаны 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 к методу из того же класса.