Каким образом реализовано внедрение зависимостей (Dependency Injection) в Spring?

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

Ответ

Spring реализует Dependency Injection (DI) через свой IoC-контейнер, который автоматически управляет зависимостями между бинами. Основные способы внедрения:

1. Внедрение через конструктор (Constructor Injection) — рекомендуемый способ

Зависимости передаются в качестве аргументов конструктора. Делает зависимости явными и обязательными, обеспечивает неизменяемость и удобство тестирования.

@Service
public class UserService {
    private final UserRepository userRepo;

    // Spring автоматически найдет бин UserRepository и внедрит его
    @Autowired // Начиная с Spring 4.3, для одного конструктора аннотация не обязательна
    public UserService(UserRepository userRepo) {
        this.userRepo = userRepo;
    }
}

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

Зависимости устанавливаются через setter-методы. Полезно для опциональных зависимостей или изменения зависимостей в runtime.

@Service
public class OrderService {
    private DiscountCalculator discountCalculator;

    @Autowired
    public void setDiscountCalculator(DiscountCalculator calc) {
        this.discountCalculator = calc;
    }
}

3. Внедрение через поле (Field Injection) — используйте с осторожностью

Зависимость инжектируется напрямую в поле с помощью @Autowired. Наиболее лаконичный, но наименее гибкий способ (усложняет тестирование, скрывает зависимости).

@Repository
public class UserRepositoryImpl {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

Как это работает под капотом:

  1. Spring создает контекст приложения, сканируя классы и конфигурации.
  2. Для каждого бина анализируются его зависимости (по типу, имени, аннотациям @Qualifier).
  3. Контейнер строит граф зависимостей, разрешает их (находит или создает требуемые бины) и внедряет их в целевые бины перед тем, как отдать их в использование.
  4. Внедрение может происходить через рефлексию или генерацию прокси-классов (для бинов со scope prototype или при использовании @Lookup).

Преимущества DI в Spring: снижение связанности кода, упрощение тестирования (зависимости легко подменить mock-объектами), централизованное управление конфигурацией.