Какие способы внедрения зависимостей (Dependency Injection) существуют?

«Какие способы внедрения зависимостей (Dependency Injection) существуют?» — вопрос из категории ООП, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Внедрение зависимостей (DI) реализуется тремя основными способами, каждый со своими преимуществами и недостатками:

1. Внедрение через конструктор (Constructor Injection) Наиболее предпочтительный способ

public class UserService {
    private final UserRepository repository;
    private final EmailService emailService;

    // Зависимости явно передаются через конструктор
    public UserService(UserRepository repository, EmailService emailService) {
        this.repository = repository;
        this.emailService = emailService;
    }
}

Преимущества:

  • Обязательность зависимостей (нельзя создать объект без них)
  • Неизменяемость (final поля)
  • Простота тестирования (передаём моки в конструктор)
  • Явность зависимостей

2. Внедрение через сеттеры (Setter Injection)

public class UserService {
    private UserRepository repository;

    // Зависимость устанавливается через setter
    public void setRepository(UserRepository repository) {
        this.repository = repository;
    }
}

Преимущества:

  • Возможность изменения зависимостей после создания объекта
  • Поддержка optional-зависимостей

Недостатки:

  • Объект может быть в неполном состоянии до вызова сеттеров
  • Сложнее обеспечить неизменяемость

3. Внедрение через поля (Field Injection)

@Component
public class UserService {
    @Autowired  // Spring автоматически внедряет зависимость
    private UserRepository repository;
}

Недостатки (почему не рекомендуется):

  • Скрытые зависимости (не видны в конструкторе)
  • Сложность тестирования (нужен фреймворк или рефлексия)
  • Нарушение инкапсуляции
  • Невозможность сделать поле final

4. Внедрение через методы (Method Injection)

@Component
public class UserService {
    private UserRepository repository;

    @Autowired
    public void setupRepository(UserRepository repository) {
        this.repository = repository;
    }
}

Рекомендации по выбору:

  1. Используйте Constructor Injection для обязательных зависимостей
  2. Setter Injection — для optional или изменяемых зависимостей
  3. Избегайте Field Injection в production-коде

В Spring также существует:

  • Lookup Method Injection — для prototype-бинов
  • Интерфейс ApplicationContextAware — для прямого доступа к контексту