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 выводит его из-под управления контейнера, а значит, у него не будет внедрённых зависимостей и он не получит транзакций и других возможностей. Доверяйте сборку графа объектов контейнеру — в этом и есть смысл фреймворка.

Проверьте себя
1. Что такое бин (bean) в Spring?
AЛюбой класс в проекте
BОбъект, которым управляет IoC-контейнер: создаёт, настраивает, хранит и отдаёт
CТаблица в базе данных
DФайл конфигурации
2. Сколько экземпляров бина с областью видимости по умолчанию создаёт контейнер?
AПо одному на каждый запрос
BПо одному на каждый поток
CОдин на весь контейнер (синглтон)
DНовый при каждом обращении