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

Какая проблема возникнет при Self Injection?

3.0 Senior🔥 201 комментариев
#SOLID и паттерны проектирования#Spring Framework

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

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

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

Self Injection в Spring: Проблемы и решения

Self Injection — это анти-паттерн в Spring, когда компонент инжектит сам себя. Это выглядит как простое решение на первый взгляд, но создаёт серьёзные проблемы для тестируемости, мониторинга и логики приложения. После 10 лет разработки я часто вижу этот паттерн в legacy коде и всегда пытаюсь его избежать.

Что такое Self Injection?

// ANTI-PATTERN: Класс инжектит самого себя
@Service
public class UserService {
    @Autowired
    private UserService self;  // Self injection!
    
    public void createUser(User user) {
        // Основная логика
        repository.save(user);
        
        // Вызываем метод через self
        self.notifyUser(user);  // Зачем?
    }
    
    @Transactional
    public void notifyUser(User user) {
        // Отправляем email
        emailService.send(user.getEmail());
    }
}

Основная проблема: Обход прокси-объектов

Это главная причина использования self injection, и она выявляет глубокое непонимание того, как работает Spring.

// ДО: НЕ работает благодаря прокси-объектам
@Service
public class UserService {
    @Transactional
    public void createUser(User user) {
        repository.save(user);
        notifyUser(user);  // Вызов через this!
    }
    
    @Transactional
    public void notifyUser(User user) {
        // Транзакция НЕ применится!
        // Причина: вызов через this игнорирует прокси
        emailService.send(user.getEmail());
    }
}

// ПРАВИЛО Spring:
// - Вызов через @Autowired инстанс → прокси работает ✅
// - Вызов через this → прокси НЕ работает ❌

Проблема 1: Нарушение логики Spring Proxy

// ПРОБЛЕМА: Self injection создаёт мнимое ощущение, что будто работает
@Service
public class OrderService {
    @Autowired
    private OrderService self;  // Сам себя
    
    public void processOrder(Order order) {
        // Транзакция 1: открыта
        saveOrder(order);
        
        // Надеемся, что здесь откроется новая транзакция
        self.notifyPayment(order);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void notifyPayment(Order order) {
        // На самом деле НЕ откроется новая транзакция!
        // Почему? Потому что self — это реальный объект, не прокси
        paymentService.charge(order);
    }
}

// РЕЗУЛЬТАТ: Ошибка в платёже откатит весь заказ!

Проблема 2: Сложность тестирования

// Self injection усложняет unit тесты
@Service
public class UserService {
    @Autowired
    private UserService self;  // Self injection
    
    public void registerUser(User user) {
        saveUser(user);
        self.sendWelcomeEmail(user);  // Зависит от самого себя!
    }
    
    public void sendWelcomeEmail(User user) {
        emailService.send(user.getEmail());
    }
}

// ТЕСТ: Очень сложно мокировать
@Test
public void testRegisterUser() {
    UserService userService = new UserService();
    // Как мокировать sendWelcomeEmail, если она вызывается через self?
    // Нельзя просто заменить userService.sendWelcomeEmail = mock
    // Приходится создавать сложные шпионские объекты
    
    UserService spyService = Mockito.spy(userService);
    spyService.registerUser(new User());
    // Проблема: spy работает плохо с self injection
}

Проблема 3: Неопределённое поведение и race conditions

// Self injection может привести к непредсказуемому поведению
@Service
public class PaymentService {
    @Autowired
    private PaymentService self;  // Self injection
    
    public void processPayment(Payment payment) {
        self.validatePayment(payment);
        // Вызов через self может быть обойдён в конкурентной среде
    }
    
    @Transactional
    public void validatePayment(Payment payment) {
        // Если два потока одновременно вызывают processPayment
        // Поведение может отличаться
    }
}

Проблема 4: Цикличные зависимости

// Self injection может привести к проблемам инициализации
@Service
public class ServiceA {
    @Autowired
    private ServiceA self;  // Self injection
    
    @Autowired
    private ServiceB serviceB;  // Зависит от B
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;  // B зависит от A
}

// РЕЗУЛЬТАТ: Циклическая зависимость, ошибка инициализации
// BeanCurrentlyInCreationException

Правильные решения

Решение 1: Рефакторинг в отдельный сервис

// ВМЕСТО self injection — разделяй ответственность
@Service
public class UserService {
    @Autowired
    private UserRepository repository;
    
    @Autowired
    private NotificationService notificationService;  // Внешний сервис
    
    public void createUser(User user) {
        repository.save(user);
        notificationService.notifyUser(user);
    }
}

@Service
public class NotificationService {
    @Autowired
    private EmailService emailService;
    
    @Transactional
    public void notifyUser(User user) {
        emailService.send(user.getEmail());
    }
}

// ПРЕИМУЩЕСТВА:
// - Чёткое разделение ответственности
// - Легко тестировать: мокируем NotificationService
// - Spring прокси работает правильно
// - Транзакции работают корректно

Решение 2: ApplicationContext для явного вызова

// ЕСЛИ ОЧЕНЬ НУЖЕН self, используй ApplicationContext
@Service
public class UserService implements ApplicationContextAware {
    @Autowired
    private UserRepository repository;
    
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.applicationContext = context;
    }
    
    public void createUser(User user) {
        repository.save(user);
        
        // Получи прокси через контекст
        UserService proxy = applicationContext.getBean(UserService.class);
        proxy.notifyUser(user);  // Теперь прокси РАБОТАЕТ
    }
    
    @Transactional
    public void notifyUser(User user) {
        emailService.send(user.getEmail());
    }
}

// Это лучше, чем self injection, но всё ещё не идеально

Решение 3: ObjectProvider для ленивой загрузки

// Используй ObjectProvider для более элегантного решения
@Service
public class UserService {
    @Autowired
    private UserRepository repository;
    
    @Autowired
    private ObjectProvider<UserService> userServiceProvider;
    
    public void createUser(User user) {
        repository.save(user);
        
        // Получи прокси через ObjectProvider
        userServiceProvider.getIfAvailable()
            .ifPresent(service -> service.notifyUser(user));
    }
    
    @Transactional
    public void notifyUser(User user) {
        emailService.send(user.getEmail());
    }
}

Решение 4: Правильная архитектура

// ЛУЧШИЙ ПОДХОД: Правильная архитектура

// 1. Разделяй на слои
@Service
public class UserApplicationService {  // Application layer
    @Autowired
    private UserDomainService userDomainService;
    
    @Autowired
    private NotificationService notificationService;
    
    @Transactional
    public void registerUser(UserDTO dto) {
        User user = userDomainService.create(dto);
        notificationService.sendWelcome(user);
    }
}

@Service
public class UserDomainService {  // Domain layer
    @Autowired
    private UserRepository repository;
    
    public User create(UserDTO dto) {
        User user = new User(dto);
        return repository.save(user);
    }
}

@Service
public class NotificationService {  // Application layer
    @Autowired
    private EmailService emailService;
    
    public void sendWelcome(User user) {
        emailService.send(user.getEmail(), "Welcome!");
    }
}

// ПРЕИМУЩЕСТВА:
// - Чёткая архитектура
// - Легко расширять
// - Простое тестирование
// - Никакие self injection не нужны

Когда МОЖЕТ быть self injection?

Очень редко, но есть легитимные case'ы:

// 1. Кеширование через аспект
@Service
public class CachedService {
    @Autowired
    private CachedService self;
    
    public void process() {
        self.cachedMethod();  // Кеширование через прокси
    }
    
    @Cacheable("data")
    public String cachedMethod() {
        // Это работает, если используется прокси
        return expensiveOperation();
    }
}

// 2. Обход циклических зависимостей (очень редко)
// Но лучше пересмотри архитектуру

Как выявить self injection в коде?

// Паттерн для поиска в IDE:
// Ищи: @Autowired.*Self
// или: @Autowired.*this.class

// Проверь:
git grep -n "@Autowired" | grep -i "self\|@Service.*\n.*@Autowired.*this"

Правила для избежания

1. Никогда не инжектируй сам свой класс

// ❌ ПЛОХО
@Autowired
private UserService self;

// ✅ ХОРОШО
@Autowired
private OtherService otherService;

2. Используй отдельные сервисы для разных ответственностей

// ❌ ПЛОХО: Одна большая служба
@Service
public class UserService {
    public void createUser() { }
    public void notifyUser() { }
    public void auditUser() { }
    public void reportUser() { }
}

// ✅ ХОРОШО: Разделённые сервисы
@Service
public class UserService { }
@Service
public class UserNotificationService { }
@Service
public class UserAuditService { }
@Service
public class UserReportService { }

3. Если нужно вызвать метод с AOP — переделай

// ❌ ПЛОХО: Self injection для транзакции
@Service
public class UserService {
    @Autowired
    private UserService self;
    
    public void register(User user) {
        self.saveInTransaction(user);
    }
    
    @Transactional
    public void saveInTransaction(User user) { }
}

// ✅ ХОРОШО: Извлеки логику
@Service
public class UserService {
    @Transactional
    public void register(User user) {
        // Всё уже в транзакции
    }
}

Вывод

Self injection — это anti-pattern в Spring, создающий:

  • Проблемы с прокси-объектами
  • Сложности при тестировании
  • Непредсказуемое поведение транзакций
  • Нарушение принципов SOLID

Вместо self injection:

  • Разделяй ответственность на разные сервисы
  • Используй правильную архитектуру
  • Инжектируй внешние сервисы через конструктор
  • Применяй DDD принципы

Чистый код без self injection намного проще поддерживать и расширять.