Как устроен жизненный цикл Spring бина и как в него встроиться
Что проверяют
Знаете ли вы порядок этапов и точки, куда можно вклиниться. Вопрос отделяет тех, кто читал документацию, от тех, кто ставил аннотации по образцу.
Как отвечать
Порядок такой: контейнер читает определение бина, создаёт экземпляр, внедряет зависимости, вызывает Aware-интерфейсы, затем BeanPostProcessor.postProcessBeforeInitialization, затем инициализацию, затем postProcessAfterInitialization (именно здесь бин чаще всего заворачивается в прокси), и бин готов. При остановке контекста вызывается разрушение.
Куда встроиться при инициализации, по предпочтительности:
@PostConstruct: стандартная аннотация, самый обычный способ;InitializingBean.afterPropertiesSet: то же самое, но привязывает код к Spring;@Bean(initMethod = "..."): когда класс чужой и менять его нельзя.
При разрушении симметрично: @PreDestroy, DisposableBean.destroy, destroyMethod. Важно помнить, что у бинов с областью prototype разрушение не вызывается вовсе: контейнер отдаёт объект и забывает о нём.
Более мощные точки: BeanPostProcessor (вмешаться в каждый бин), BeanFactoryPostProcessor (изменить определения до создания), ApplicationListener и @EventListener(ApplicationReadyEvent.class) для действий после полного старта.
Практическая тонкость: внутри @PostConstruct прокси ещё не готов, поэтому @Transactional и @Async на вызовах оттуда не работают. Долгую работу при старте вешают на событие готовности контекста, а не на конструктор бина.
Где спотыкаются
Делают в конструкторе тяжёлую работу и замедляют старт.
Ждут срабатывания @PreDestroy у prototype-бина.
Пытаются вызвать транзакционный метод из @PostConstruct.
Не знают, что прокси создаётся именно на этапе после инициализации.