Жизненный цикл бина

Spring bean — объект, создание и жизненный цикл которого контролирует IoC контейнер. Важно различать целевой объект и объект, который в итоге получает потребитель: после постобработки контейнер может вернуть прокси.

Последовательность создания

Для обычного singleton-бина этапы выглядят так:

  1. Spring читает BeanDefinition и разрешает зависимости конструктора.
  2. Вызывается конструктор.
  3. Заполняются поля и setter-зависимости.
  4. Вызываются aware-callbacks: например, BeanNameAware.
  5. BeanPostProcessor.postProcessBeforeInitialization обрабатывает объект.
  6. Выполняются методы инициализации.
  7. BeanPostProcessor.postProcessAfterInitialization может обернуть объект прокси.
  8. Готовый бин помещается в контекст и выдаётся зависимым объектам.

Упрощённая схема:

flowchart LR
    A["BeanDefinition"] --> B["Создание объекта"]
    B --> C["Внедрение зависимостей"]
    C --> D["Aware callbacks"]
    D --> E["BPP before"]
    E --> F["Инициализация"]
    F --> G["BPP after / proxy"]
    G --> H["Готовый bean"]

Инициализация

Если применены все способы, порядок такой:

  1. метод с @PostConstruct;
  2. InitializingBean.afterPropertiesSet();
  3. пользовательский initMethod.
@Component
final class DictionaryCache {
    private final DictionaryRepository repository;
    private Map<String, String> values;
 
    DictionaryCache(DictionaryRepository repository) {
        this.repository = repository;
    }
 
    @PostConstruct
    void load() {
        values = Map.copyOf(repository.findAll());
    }
}

Конструкторная инъекция предпочтительна: объект нельзя создать без обязательной зависимости. Но обращения к БД и сети в @PostConstruct замедляют и делают хрупким запуск всего приложения. Для тяжёлой загрузки нужен осознанный механизм старта и понятное поведение при ошибке.

BeanPostProcessor и прокси

BeanPostProcessor работает с экземплярами бинов. На этом расширении основаны обработка многих аннотаций и создание AOP-прокси.

@Transactional, @Async и часть других возможностей срабатывают при вызове через готовый прокси. Внутри @PostConstruct прокси ещё не должен считаться рабочей границей. Вызов собственного транзакционного метода из того же объекта также обходит прокси — это self-invocation. См. Spring Proxy.

BeanFactoryPostProcessor действует раньше и изменяет определения бинов, а не созданные экземпляры.

Завершение

При штатном закрытии контекста для singleton-бина вызываются:

  1. @PreDestroy;
  2. DisposableBean.destroy();
  3. пользовательский destroyMethod.
@PreDestroy
void close() {
    executor.shutdown();
}

Контейнер не отслеживает полный жизненный цикл prototype-бинов после выдачи клиенту. Освобождение их ресурсов — ответственность потребителя.

Scope и ленивое создание

  • singleton — один экземпляр на ApplicationContext, а не на JVM;
  • prototype — новый экземпляр при каждом запросе у контейнера;
  • web-scopes привязаны к request, session и другим web-контекстам;
  • @Lazy откладывает создание до первого обращения.

Если singleton напрямую получает prototype, prototype будет разрешён только при создании singleton. Для нового экземпляра на каждый вызов используют ObjectProvider, factory или scoped proxy.

Частые сбои

  • тяжёлая работа или внешний вызов в конструкторе;
  • циклическая зависимость, скрытая field-injection;
  • ожидание AOP в @PostConstruct;
  • незакрытый executor, клиент или пул соединений;
  • обращение к ленивому бину уже во время остановки контекста.

На собеседовании

Когда бин считается готовым? После инициализации и postProcessAfterInitialization; именно тогда потребитель обычно получает окончательный объект или прокси.

Чем @PostConstruct отличается от конструктора? В конструкторе уже доступны constructor-зависимости, но field/setter-инъекция и постобработка ещё не завершены. @PostConstruct выполняется после заполнения зависимостей.

Кто создаёт транзакционный прокси? Один из BeanPostProcessor, работающий после инициализации целевого объекта.

Связанные заметки: Spring Context, Aware интерфейсы, Spring AOP, Transactional.