Как вмешаться в частную жизнь Spring бина

springbean-lifecycleВ интернете полно картинок типа той, что слева, о жизненном цикле бина и как им пользоваться. И почти к каждой такой картинке прилагается длинная статья, рассказывающая о этапах создания бина и как там всё устроено.

Мне кажется, что начинающему Spring разработчику гораздо полезнее знать, как пользоваться этими этапами создания, чем о их существовании.

Подготовка

Возьмём пустой maven проект с JUnit, Hamcrest, Spring и Grovvy для конфигурации контекста:

Создание бина

Начну с самого интересного: выполняем код после создания или перед уничтожением бина. Вообще с созданием может возникнуть непонимание, так как вроде бы есть конструктор, который и занимается созданием эксземпляра класса (=созданием бина) и вроде бы в нём и надо делать инициализацию, нет?

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

Впрочем меньше слов, больше кода. Существует три возможности для указания метода инициализации бина, аннотация, реализация особого интерфейса, задание метода в конфигурации:

Для последнего случая никаких особенных дополнений в коде не требуется. В случае, если вы предпочитаете xml, а не groovy, задание init метода выглядит несколько иначе:

Результат во всех трёх случаях одинаков:

Уничтожение бина

Spring предоставляет возможность выполнения кода перед уничтожением бина. И, так же как и с finalize(), не гарантирует, что код будет вызван. Как и с инициализацией бина, есть три метода вызова кода при унчитожении бина:

Для последнего случая никаких особенных дополнений в коде не требуется. В случае, если вы предпочитаете xml, а не groovy, задание destroy метода выглядит несколько иначе:

В отличие от init() методов, с destroy() методами есть проблема с вызовом. Методы destroy() не вызываются автоматически при интеграционном тестировании с SpringJUnit4ClassRunner, они не вызываются автоматически, если контекст создаётся вручную. В последнем случае контекст надо и завершать тоже вручную:

Также destroy() методы не будут вызываны для prototype scope бинов.

Имя бина

Используя интерфейсы создания бина можно узнать, какое бину досталось имя:

*Aware интерфейсы

Spring определяет огромное количество *Aware интерфейсов, внедряющих в бины тот или иной объект. Большая часть этих объектов может быть так же внедрена и с использованием @Inject/@Autowired. Например вместо использования интерфейсов ApplicationContextAware и BeanFactoryAware можно просто написать:

Список *Aware интерфейсов весьма длинный и достаточно однообразный, поэтому вместо того, чтобы описывать каждый интерфейс с примером, я обойдусь сводным списком:

  • ApplicationContextAware.setApplicationContext() — позволяет узнать ApplicationContext, в котором создан бин.
  • ApplicationEventPublisherAware.setApplicationEventPublisher() — позволяет узнать ApplicationEventPublisher, с которым исполняется бин.
  • BeanClassLoaderAware.setBeanClassLoader() — позволяет узнать BeanClassLoader, с котором создан бин
  • BeanFactoryAware.setBeanFactory() — позволяет узнать BeanFactory, с которой создан бин
  • BootstrapContextAware.setBootstrapContext() —  позволяет узнать BootstrapContext, с которым исполняется бин
  • LoadTimeWeaverAware.setLoadTimeWeaver() — позволяет узнать LoadTimeWeaver, связанный с ApplicationContext, в котором создан бин
  • MessageSourceAware.setMessageSource() — позволяет узнать MessageSource, с которым исполняется бин
  • NotificationPublisherAware.setNotificationPublisher() — позволяет узнать NotificationPublisher для текущего экземпляра управления ресурсами
  • PortletConfigAware.setPortletConfig() — позволяет узнать PortletConfig, с которым исполняется бин
  • PortletContextAware.setPortletContext() — позволяет узнать PortletContext, с которым исполняется бин
  • ResourceLoaderAware.setResourceLoader() — позволяет узнать ResourceLoader, с которым исполняется бин
  • ServletConfigAware.setServletConfig() — позволяет узнать  ServletConfig, с которым исполняется бин
  • ServletContextAware.setServletContext() — позволяет узнать ServletContext, с которым исполняется бин

Код примера доступен на github.