Spring Dependency Injection и Inversion of Control
Inversion of Control
Inversion of Control, IoC — общий принцип: не прикладной код управляет созданием и связыванием всех компонентов, а внешний механизм вызывает код в нужный момент. В Spring таким механизмом служит IoC контейнер.
Контейнер:
- читает конфигурацию и находит определения бинов;
- создаёт объекты;
- разрешает их зависимости;
- управляет scope и жизненным циклом;
- применяет расширения, включая post-processors и proxy.
Dependency Injection
Dependency Injection, DI — способ реализовать IoC: объект получает зависимости снаружи и не ищет их самостоятельно.
interface PriceProvider {
BigDecimal priceFor(long productId);
}
@Service
final class OrderCalculator {
private final PriceProvider prices;
OrderCalculator(PriceProvider prices) {
this.prices = prices;
}
BigDecimal total(long productId, int quantity) {
return prices.priceFor(productId)
.multiply(BigDecimal.valueOf(quantity));
}
}OrderCalculator знает контракт PriceProvider, но не создаёт реализацию и не обращается к контейнеру. В unit-тесте можно передать stub без Spring.
DI — не Service Locator
При Service Locator объект сам запрашивает зависимость:
PriceProvider prices = context.getBean(PriceProvider.class);Зависимость становится скрытой, класс связывается с контейнером, а ошибка обнаруживается во время выполнения. При constructor injection зависимость видна в сигнатуре и проверяется в момент создания объекта.
Spring-контейнер внутри действительно ищет подходящие бины, но это внутренняя реализация контейнера, а не Service Locator в бизнес-классе.
Виды внедрения
Constructor injection — основной вариант для обязательных зависимостей. Позволяет делать поля final, упрощает тестирование и быстро показывает цикл зависимостей.
Setter injection подходит для действительно необязательной или изменяемой настройки.
Field injection короче, но скрывает контракт класса, мешает обычному созданию объекта и допускает изменяемое состояние. В прикладном коде её лучше избегать.
При единственном конструкторе @Autowired не требуется.
Как выбирается бин
По умолчанию Spring сопоставляет зависимость по типу. Если кандидатов несколько:
@Service
final class PaymentService {
PaymentService(@Qualifier("fastGateway") PaymentGateway gateway) {
// ...
}
}Варианты разрешения неоднозначности:
@Qualifier— явно назвать смысл кандидата;@Primary— выбрать реализацию по умолчанию;- внедрить
List<PaymentGateway>илиMap<String, PaymentGateway>; - изменить дизайн, если выбор реализации является бизнес-решением.
Отсутствующая обязательная зависимость и несколько равноправных кандидатов должны приводить к ошибке старта, а не к случайному выбору.
Scope и зависимости
Singleton не должен напрямую хранить request-scoped объект без scoped proxy/provider. Иначе жизненные циклы несовместимы. Для отложенного или множественного получения можно внедрить ObjectProvider<T>, но не превращать его в скрытый Service Locator.
На собеседовании
IoC и DI — одно и то же? IoC — более широкий принцип, DI — конкретный способ передать управление созданием и связыванием объектов.
Почему constructor injection лучше field injection? Зависимости явные, объект валиден после конструктора, поля могут быть final, тест не требует reflection или контейнера.
Что произойдёт при двух реализациях интерфейса? Spring сообщит о неоднозначности, если выбор не задан через qualifier, primary или структуру зависимости.
Связанные заметки: Spring Context, Способы внедрения зависимостей Spring, Жизненный цикл бина.