IoC-контейнер и бины: сердце Spring
Spring сам создаёт объекты вашего приложения и хранит их в контейнере. Эта идея — инверсия управления (IoC) — лежит в основе всего фреймворка.
Суть: вы не вызываете new для сервисов и репозиториев — это делает контейнер. Объекты под его управлением называются бинами, и контейнер сам связывает их между собой.
В обычном Java-коде объект A, которому нужен объект B, создаёт его сам: B b = new B(). Это жёсткая связь: A знает конкретный класс B, его конструктор, его зависимости. Заменить B на тестовую заглушку невозможно без правки A. Инверсия управления переворачивает эту схему: создание объектов и их связывание берёт на себя внешняя сила — контейнер.
Что такое бин и контейнер
Бин — это объект, которым управляет Spring: создаёт, настраивает, хранит и отдаёт по запросу. IoC-контейнер (он же ApplicationContext) — реестр всех бинов. При старте Spring сканирует классы, находит помеченные специальными аннотациями, создаёт из них бины и складывает в контейнер.
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String greet() {
return "Привет от сервиса";
}
}
Аннотация @Service говорит: «сделай из этого класса бин». Теперь Spring сам создаст единственный экземпляр UserService и будет отдавать его всем, кому он нужен.
Как работает под капотом
При старте контекст проходит фазы: сканирование классов → создание определений бинов (BeanDefinition) → создание экземпляров → внедрение зависимостей. По умолчанию каждый бин — синглтон: один экземпляр на весь контейнер. Когда контроллеру нужен сервис, контейнер не создаёт новый, а отдаёт уже существующий.
Старт приложения
|
v
IoC-контейнер (ApplicationContext)
┌──────────────────────────────────┐
│ @Service UserService (синглтон)│
│ @Repository UserRepo (синглтон)│
│ @RestController UserCtrl │
└──────────────────────────────────┘
| по запросу отдаёт готовый бин
v
UserController <- получает UserService
UserService <- получает UserRepo
Смоделируем мини-контейнер. По сути это реестр «имя → объект», который умеет создавать и отдавать синглтоны:
# Мини IoC-контейнер: создаёт и хранит бины-синглтоны
class Container:
def __init__(self):
self._instances = {}
self._factories = {}
def register(self, name, factory):
self._factories[name] = factory
def get(self, name):
if name not in self._instances: # синглтон: создаём один раз
self._instances[name] = self._factories[name](self)
return self._instances[name]
c = Container()
c.register("repo", lambda ctx: {"users": ["Анна", "Борис"]})
c.register("service", lambda ctx: ("service using", ctx.get("repo")))
s1 = c.get("service")
s2 = c.get("service")
print(s1)
print("Тот же экземпляр?", s1 is s2) # True — синглтон
Запустите «Попробуй сам ▶»: контейнер создаёт бин один раз и отдаёт тот же экземпляр повторно — ровно как Spring с синглтонами.
Частые ошибки
- Создавать бины через new.
new UserService()создаёт объект вне контейнера — у него не будет внедрённых зависимостей. - Хранить состояние в синглтон-бине. Один экземпляр на всех; изменяемое поле станет общим для всех запросов и вызовет гонки.
- Забыть стерео-аннотацию. Без
@Service/@Componentкласс не станет бином, и его не получится внедрить.
Best practices
- Доверяйте создание объектов контейнеру — не вызывайте
newдля сервисов и репозиториев. - Держите бины без изменяемого состояния (stateless) — это безопасно для синглтонов.
- Помечайте классы осмысленно:
@Service,@Repository,@Component.
Итог: IoC-контейнер — сердце Spring. Он создаёт бины, хранит их (по умолчанию синглтонами) и связывает между собой. Вы описываете, что нужно, а контейнер собирает граф объектов. Дальше — как именно зависимости попадают внутрь.
Закрепим главное
Инверсия управления переворачивает привычную ответственность: не объект создаёт свои зависимости, а контейнер создаёт объект и снабжает его всем необходимым. Эта, казалось бы, простая идея — фундамент тестируемости и гибкости всего Spring. Когда зависимости приходят извне, их легко подменить заглушками в тестах и легко заменить реализацию в продакшене, не трогая код потребителя.
Крепко усвойте две вещи про синглтоны. Первая: один экземпляр бина обслуживает все запросы одновременно, поэтому хранить в нём изменяемое состояние нельзя — это прямой путь к гонкам данных между потоками. Делайте бины stateless. Вторая: создание объекта через new выводит его из-под управления контейнера, а значит, у него не будет внедрённых зависимостей и он не получит транзакций и других возможностей. Доверяйте сборку графа объектов контейнеру — в этом и есть смысл фреймворка.