Что такое predestroy и postconstruct в spring
6 способов выполнения метода при старте Spring Boot приложения
При разработке на Spring Boot иногда нам нужно выполнить метод или фрагмент кода при запуске приложения. Этот код может быть любым, от записи определенной информации до настройки базы данных, заданий cron и т. д. Мы не можем просто поместить этот код в конструктор, потому что требуемые переменные или службы могут быть еще не инициализированы. Это может привести к null pointer исключению или некоторым другим.
Зачем нам нужно запускать код при запуске Spring Boot?
Нам нужно запускать метод при запуске приложения по многим причинам, например, для:
Записи важных событий или сообщения о запуске приложения
Обработки базы данных или файлов, индексации, создания кэшей и т. д.
Запуска фоновых процессов, таких как отправка уведомления, выборка данных из некоторой очереди и т. д.
Различные способы запуска метода после запуска Spring Boot
У каждого способа есть свои преимущества. Давайте рассмотрим подробнее, чтобы решить, что нам следует использовать:
события Spring Boot Application
аннотацию @Postconstruct для метода
атрибут инициализации аннотации @bean
1. Использование интерфейса CommandLineRunner
В процессе запуска после инициализации контекста Spring boot вызывает свой метод run() с аргументами командной строки, предоставленными приложению.
Чтобы сообщить Spring Boot о нашем интерфейсе commandlineRunner, мы можем либо реализовать его и добавить аннотацию @Component над классом, либо создать его bean-компонент с помощью @bean.
Пример реализации интерфейса CommandLineRunner
Пример создания bean-компонента интерфейса CommandLineRunner
Мы можем запустить приложение из командной строки или IDE. Давайте рассмотрим пример, когда мы запускаем приложение с аргументами «–status = running».
Это дает следующий вывод журнала:
Как мы видим, параметр не анализируется, а вместо этого интерпретируется как одно значение «status = running».
Чтобы получить доступ к аргументам командной строки в проанализированном формате, нам нужно использовать интерфейс ApplicationRunner. Мы рассмотрим это чуть позже.
Spring Boot добавляет интерфейс CommandLineRunner в процесс запуска. Следовательно, выброшенное исключение в commandlineRunner заставит процесс загрузки Spring прервать запуск.
Мы можем создать несколько CommandLineRunner в одном приложении. Используя интерфейс Ordered или аннотацию @Order, мы можем настроить порядок, в котором они должны запускаться. Меньшее значение означает более высокий приоритет. По умолчанию все компоненты создаются с самым низким приоритетом. Поэтому компоненты не указанные конфигурации order будут вызываться последними.
Мы можем использовать аннотацию @Order, как показано ниже
2. Использование интерфейса ApplicationRunner
Как обсуждалось ранее, для доступа к анализируемым аргументам нам необходимо использовать интерфейс ApplicationRunner. Интерфейс ApplicationRunner предоставляет метод запуска с ApplicationArguments вместо необработанного массива строк.
Он предоставляет различные способы доступа к аргументам, как показано ниже.
Предоставляет необработанные аргументы, которые были переданы приложению
Именам всех необязательных аргументов предшествует «-», например: –name = «stacktrace»
Возвращает необработанные необязательные аргументы. Аргументы без «-»
boolean containsOption(String name)
Проверяет, присутствует ли имя в необязательных аргументах или нет
List getOptionValues(String name)
Предоставляет значение аргумента по имени
Метод getOptionValues возвращает список значений, потому что значение аргумента может быть массивом, поскольку мы можем использовать один и тот же ключ более одного раза в командной строке.
Пример реализации интерфейса Application runner
Давайте запустим приведенную ниже программу, используя аргументы «status = running –mood = happy 10–20», и давайте разберемся с результатом.
CommandLineRunner и ApplicationRunner имеют схожие функции, например:
Исключение в методе run() прервет запуск приложения.
Несколько ApplicationRunner можно заказать с помощью упорядоченного интерфейса или аннотации @Order.
Важнее всего отметить, что порядок разделяется между CommandLineRunners и ApplicationRunners. Это означает, что порядок выполнения может быть смешанным между commandlineRunner и applicationRunner.
3. Событие приложения в Spring Boot
Фреймворк Spring запускает разные события в разных ситуациях. Он также вызывает множество событий в процессе запуска. Мы можем использовать эти события для выполнения нашего кода, например, ApplicationReadyEvent можно использовать для выполнения кода после запуска приложения загрузки Spring.
Если нам не нужны аргументы командной строки, это лучший способ выполнить код после запуска приложения.
Некоторые наиболее важные события spring boot,
ApplicationContextInitializedEvent: запускается после подготовки ApplicationContext и вызова ApplicationContextInitializers, но до загрузки определений bean-компонентов
ApplicationPreparedEvent: запускается после загрузки определений bean-компонентов
ApplicationStartedEvent: запускается после обновления контекста, но до вызова командной строки и запуска приложений.
ApplicationReadyEvent: запускается после вызова любого приложения и запуска командной строки
ApplicationFailedEvent: срабатывает, если есть исключение при запуске
Можно создать несколько ApplicationListeners. Их можно упорядочивать с помощью аннотации @Order или с помощью интерфейса Ordered.
Order используется совместно с другими ApplicationListeners того же типа, но не с ApplicationRunners или CommandLineRunners.
4. Аннотация метода @Postconstruct
Метод можно пометить аннотацией @PostConstruct. Всякий раз, когда метод отмечен этой аннотацией, он будет вызываться сразу после внедрения зависимости.
Метод @PostConstruct связан с конкретным классом, поэтому его следует использовать только для кода конкретного класса. В каждом классе может быть только один метод с аннотацией postConstruct.
Следует отметить, что, если класс помечен как lazy, это означает, что класс создается по запросу. После этого будет выполнен метод, помеченный аннотацией @postConstruct.
Метод, отмеченный аннотацией postConstruct, может иметь любое имя, но не должен иметь никаких параметров. Он должен быть void и не должен быть static.
Обратите внимание, что аннотация @postConstruct является частью модуля Java EE и помечена как устаревшая в Java 9 и удалена в Java 11. Мы все еще можем использовать ее, добавив java.se.ee в приложение.
5. Интерфейс InitializingBean
Решение InitializingBean работает точно так же, как аннотация postConstruct. Вместо использования аннотации мы должны реализовать интерфейс InitializingBean. Затем нам нужно переопределить метод void afterPropertiesSet().
InitializingBean является частью пакета org.springframework.beans.factory.
Вы можете подумать, что же произойдет, если мы будем использовать одновременно аннотацию @PostConstruct и InitializingBean. В этом случае метод @PostConstruct будет вызываться перед методом afterPropertiesSet() InitializingBean.
6. Атрибут Init аннотации @bean
Мы можем реализовать метод, используя свойство initMethod в аннотации @Bean. Этот метод будет вызываться после инициализации bean-компонента.
Метод, предоставленный в initMethod, должен быть void и не иметь аргументов. Этот метод может быть даже private.
Если у вас есть реализация InitializingBean и свойство initMethod аннотации @Bean для того же класса, то метод afterPropertiesSet для InitializingBean будет вызываться перед initMethod.
Сочетание разных подходов
Наконец, иногда нам может потребоваться объединить несколько вариантов. При этом они будут выполняться в следующем порядке:
Метод инициализации Bean-компонента
ApplicationRunner или CommandLineRunner в зависимости от порядка
Краткий обзор
Есть разные способы запустить код после запуска Spring Boot приложения
Мы можем использовать CommandLineRunner или ApplicationRunner Interface.
Используйте интерфейс ApplicationRunner для доступа к анализируемым аргументам вместо массива необработанных строк
Событие загрузки Spring выполняет код при запуске приложения
Метод, отмеченный аннотацией @PostConstruct, выполняется после инициализации объекта
Метод afterPropertiesSet() интерфейса InitializingBean вызывается после инициализации объекта.
В аннотации @Bean есть атрибут «initMethod» для предоставления метода, который будет вызываться после инициализации bean-компонента.
Подготовка к Spring Professional Certification. Контейнер, IoC, бины
Доброго времени суток, Хабр.
Сегодня я решил представить вам перевод цикла статей для подготовки к Spring Professional Certification.
Это перевод только первой статьи, если он зайдет аудитории, я продолжу выпуск переводов.
Внедрение зависимостей — это специальный паттерн, который уменьшает связь между Spring компонентами. Таким образом, при применении DI, ваш код становится чище, проще, его становится легче понять и тестировать.
Согласно паттерну DI, создание объектов для зависимостей переходит на фабрику или отдается третьей стороне. Это означает, что мы можем сосредоточиться на использовании этих объектов вместо их создания.
В Spring Framework интерфейс org.springframework.factory.BeanFactory предоставляет фабрику для бинов, которая в то же время является IoC контейнером приложения. Управление бинами основано на конфигурации(java или xml).
Интерфейс org.springframework.context.ApplicationContext — это обертка над bean factory, предоставляющая некоторые дополнительные возможности, например AOP, транзакции, безопасность, i18n, и т.п.
Основа Spring Framework — контейнер, и наши объекты «живут» в этом контейнере.
Контейнер обычно создает множество объектов на основе их конфигураций и управляет их жизненным циклом от создания объекта до уничтожения.
Контейнер — это объект, реализующий интерфейс ApplicationContext.
Spring обеспечивает несколько разновидностей контекста.
Есть несколько основных реализаций интерфейса ApplicationContext:
Примеры создания контекста:
Если вы используете JUnit 5, то вам нужно указать 2 аннотации:
Если это не веб-приложение, то есть 2 способа:
В Spring Boot приложении:
Этот класс поместит в контейнер экземпляр класса DataSource. Позднее его можно будет использовать при доступе к базе данных.
Component scanning(сканирование компонентов) — Spring автоматически обнаруживает бины, которые будут находиться в контейнере. Это бины с аннотациями-стереотипами.
Component | Корневая аннотация, которая помечает класс как кандидат для автовнедрения |
Controller | Указывает, что класс является контроллером для отправления данных на фронт. |
@RestController | Указывает, что класс является контроллером для REST. Содержит аннотации Controller и @ResponseBody |
Service | Указывает, что класс является сервисом для выполнения бизнес-логики |
Repository | Указывает, что класс является репозиторием для работы с бд |
@Configuration | Указывает, что класс содержит Java-конфигурацию(@Bean-методы) |
Область видимости — scope, скоуп. Существует 2 области видимости по умолчанию.
Singleton | Область видимости по умолчанию. В контейнере находится всего 1 экземпляр бина |
Prototype | В контейнере может находится любое количество экземпляров бина |
И 4 области видимости в веб-приложении.
Request | Область видимости — 1 HTTP запрос. На каждый запрос создается новый бин |
Session | Область видимости — 1 сессия. На каждую сессию создается новый бин |
Application | Область видимости — жизненный цикл ServletContext |
WebSocket | Область видимости — жизненный цикл WebSocket |
Область видимости указывается с помощью аннотации @Scope на @Bean методах.
Prototype Scope не потокбезопасный, т.к. он не гарантирует что один и тот же экземпляр будет вызываться только в 1 потоке.
Singleton Scope же наоборот потокобезопасный.
Singleton-бины обычно создаются сразу при сканировании.
Prototype-бины обычно создаются только после запроса.
Singleton bean можно внедрять в любой другой бин.
Prototype может быть зависимостью для любого бина.
Внедрять можно только singleton или prototype.
Для того чтобы использовать кастомный BFPP. Вы можете переопределить механизм получения данных из метафайлов.
Есть 3 варианта для создания таких методов:
Ниже перечислены типы DI, которые могут быть использованы в вашем приложении:
DI через конструктор считается самым лучшим способом, т.к. для него не надо использовать рефлексию, а также он не имеет недостатков DI через сеттер.
DI через поле не рекомендуется использовать, т.к. для этого применяется рефлексия, снижающая производительность.
DI через конструктор может приводить к циклическим зависимостям. Чтобы этого избежать, можно использовать ленивую инициализацию бинов или DI через сеттер.
Контейнер обрабатывает DI с помощью AutowiredAnnotationBeanPostProcessor. В связи с этим, аннотация не может быть использована ни в одном BeanFactoryPP или BeanPP.
Если внедряемый объект массив, коллекция, или map с дженериком, то Spring внедрит все бины подходящие по типу в этот массив(или другую структуру данных). В случае с map ключом будет имя бина.
Вы можете использовать разные типы внедрения:
Spring предоставляет аннотацию Qualifier, чтобы преодолеть проблему неоднозначности при DI.
Если в контейнере есть несколько бинов одного типа(SomeClass), то контейнер внедрит именно тот бин, над @Bean-методом которого стоит соответствующий квалификатор. Также можно не ставить квалификатор на метод, а использовать имя бина в качестве параметра квалификатора.
Имя бина можно можно указать через параметр аннотации Bean, а по умолчанию это имя фабричного метода.
Прокси это специальный объект, который имеет такие же публичные методы как и бин, но у которого есть дополнительная функциональность.
Два вида прокси:
Если в контейнере нет экземпляра бина, то вызывается @Bean-метод. Если экземпляр бина есть, то возвращается уже созданный бин.
В эту переменную будет внедрена строка, например из property или из view.
Как обычно, просьба присылать правки или найденные ошибки в личку.
Spring @PostConstruct and @PreDestroy example
Introduction
@PostConstruct and @PreDestroy annotations are not exclusive to Spring: they are a standard and consequently widely used in many container managed environments. Spring container is no exception so you can use these annotations in your Spring beans.
@PostConstruct annotation defines a method that will be called after a bean as been fully initialized. In other words it will be called after bean construction and all dependency injection.
@PreDestroy annotation defines a method that will be called just before a bean is destroyed. This is usually useful for resource clean up.
This tutorial considers the following software and environment:
Configuration
Configure Maven to get the required Spring dependencies:
Now place yourself in the project directory and issue the following command to prepare your project for Eclipse:
After conclusion you can import the project into Eclipse.
@PostConstruct and @PreDestroy
Let’s define an example bean using @PostConstruct and @PreDestroy annotations:
Spring configuration file:
Testing
When we run our test the following output will be generated:
After bean initialization
Bean method called. Text is: some text
This tutorial source code can be found at the end of this page.
Download source code from this article
Related Articles
Comments
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.
He is also the author of the WiFi File Browser Android application:
Spring @PostConstruct and @PreDestroy example
By mkyong | Last updated: June 13, 2011
Viewed: 456,806 (+72 pv/w)
In Spring, you can either implements InitializingBean and DisposableBean interface or specify the init-method and destroy-method in bean configuration file for the initialization and destruction callback function. In this article, we show you how to use annotation @PostConstruct and @PreDestroy to do the same thing.
@PostConstruct and @PreDestroy
A CustomerService bean with @PostConstruct and @PreDestroy annotation
By default, Spring will not aware of the @PostConstruct and @PreDestroy annotation. To enable it, you have to either register ‘CommonAnnotationBeanPostProcessor‘ or specify the ‘ ‘ in bean configuration file,
1. CommonAnnotationBeanPostProcessor
The initIt() method (@PostConstruct) is called, after the message property is set, and the cleanUp() method (@PreDestroy) is call after the context.close();
Download Source Code
mkyong
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.
Comments
[…] Spring – @PostConstruct and @PreDestroy example | Spring […]
Hello, i try to use Spring 4.3.8 and i get another result:
Реализация Spring Framework API с нуля. Пошаговое руководство для начинающих. Часть 1
Spring Framework является одним из самых сложных фремворков для понимания и изучения. Большинство разработчиков изучают его медленно, через практические задачи и гугл. Этот подход не эффективен, так как не даёт полной картины и при этом требует больших затрат.
Я хотел бы предложить вам принципиально новый подход к изучению Спринга. Он заключается в том, что человек проходит через серию специально подготовленных туториалов и самостоятельно реализует функционал спринга. Особенность этого подхода в том, что он, помимо 100%-го понимания изучаемых аспектов Spring даёт ещё большой прирост в Java Core (Annotations, Reflection, Files, Generics).
Статья подарит вам незабываемые ощущения и позволит почувствовать себя разработчиком Pivotal. Шаг за шагом, вы сделаете ваши классы бинами и организуете их жизненный цикл (такой же, как и в реальном спринге). Классы, которые вы будете реализовывать — BeanFactory, Component, Service, BeanPostProcessor, BeanNameAware, BeanFactoryAware, InitializingBean, PostConstruct, PreDestroy, DisposableBean, ApplicationContext, ApplicationListener, ContextClosedEvent.
Немного о себе
Меня зовут Ярослав, и я Java Developer с 4-х летним опытом работы. На данный момент я работаю в компании EPAM Systems (СПБ), и с интересом углубляюсь в те технологии, которые мы используем. Довольно часто приходится иметь дело со спрингом, и я вижу в нём некоторую золотую середину, в которой можно разиваться (Java все итак нормально знают, а слишком специфические инструменты и технологии могут приходить и уходить).
Пару месяцев назад я прошёл сертификацию Spring Professional v5.0 (без прохождения курсов). После этого я задумался над тем, как можно обучать спрингу других людей. К сожалению, на данный момент нет эффективной методики обучения. У большинства разработчиков складывается весьма поверхностное представление о фреймворке и его особенностях. Дебажить исходники спринга слишком тяжело и абсолютно не эффективно с точки зрения обучения (я как-то увлекался этим). Сделать 10 проектов? Да, вы где-то сможете углубить свои знания и получите много практического опыта, но многое из того, что «под капотом», так и не откроется перед вами. Читать книгу Spring in Action? Круто, но затратно по усилиям. Я вот проработал её 40% (во время подготовки к сертификации), но это было не просто.
Единственный способ понять что-то до конца — самостоятельно разработать это. Недавно у меня появилась идея о том, что можно провести человека через интересный туториал, который будет курировать разработку своего DI-фреймворка. Главная его особенность будет заключаться в том, что API будет совпадать с изучаемым API. Офигенность данного подхода в том, что помимо глубокого (без пробелов) понимания спринга, человек получит ОГРОМНОЕ количество опыта по Java Core. Признаюсь честно, я сам много всего нового узнал во время подготовки статьи, как по Spring, так и по Java Core. Давайте приступим к разработке!
Проект с нуля
Итак, первое, что нужно сделать — это открыть любимую IDE и создать проект с чистого листа. Никаких Maven, никаких сторонних библиотек мы подключать не будем. Даже Spring-зависимости подключать не будем. Наша цель — разработать API, максимально похожий на Spring API, и реализовать его самостоятельно.
В чистом проекте создайте 2 главных пакета. Первый пакет — ваше приложение ( com.kciray ), и класс Main.java внутри него. Второй пакет — org.springframework. Да, мы будем дублировать структуру пакетов оригинального спринга, название его классов и их методов. Есть такой интересный эффект — когда вы создаете что-то свое, это свое начинает казаться простым и понятным. Потом, когда вы будете работать в больших проектах, вам будет казаться, что там все создано на основе вашей заготовки. Такой подход может очень положительно сказаться на понимании работы системы в целом, её улучшении, исправлении багов, решении проблем и так далее.
Если будут вознить какие-то проблемы, работающий проект можете взять тут.
Создаём контейнер
Обратите внимание на то, что BeanFactory и FactoryBean — это разные вещи. Первое — это фабрика бинов (контейнер), а второе — это бин-фабрика, который сидит внутри контейнера и тоже производит бины. Фабрика внутри фабрики. Если вы путаетесь между этими определениями, можете запомнить, что в английском языке второе существительное является ведущим, а первое — служит чем-то типа прилагательного. В слове BeanFactory Главным словом является фабрика, а в FactoryBean — бин.
Теперь нам надо сделать так, чтобы наш контейнер ( BeanFactory ) обнаружил наши классы, создал их за нас и инжектировал один в другой. Операции типа new ProductService() должны находится внутри контейнера и делаться за разработчика. Давайте используем самый современный подход (сканирование классов и аннотации). Для этого нам нужно ручками создать аннотацию @Component ( пакет org.springframework.beans.factory.stereotype ).
По умолчанию аннотации не загружаются в память во время работы программы ( RetentionPolicy.CLASS ). Мы изменили данное поведение через новую политику удержания ( RetentionPolicy.RUNTIME ).
Сразу хочу предупредить, что будет много проверяемых исключений, поэтому будьте готовы их оборачивать. Но для начала, давайте определимся, чего мы хотим. Мы хотим добавить специальный метод в BeanFactory и вызывать его в Main :
Наверно вы уже заметили, что пакеты разделяются точкой, а файлы — прямым слешем. Нам надо преобразовать пакетный путь в путь к папке, и получить что-то типа List (пути в вашей файловой системе, по которым можно искать class-файлы).
Поехали дальше. Нам надо извлечь папки и проработать содержимое каждой из них. Преобразуем URL в файл, а затем получаем его имя. Тут надо отметить, что мы не будем сканировать вложенные пакеты, чтобы не усложнять код. Можете усложнить себе задачу и сделать рекурсию, если есть желание.
Дальше, нам нужно получить название файла без расширения. На дворе 2018 год, Java много лет развивала File I/O (NIO 2), но до сих пор не может отделить расширение от имени файла. Приходится свой велосипед создавать, т.к. мы решили не использовать сторонние библиотеки вроде Apache Commons. Давайте используем старый дедовский способ lastIndexOf(«.») :
Далее, мы можем по полному имени класса получить объект класса (для этого вызываем класс класса Class ):
Окей, теперь наши классы в наших руках. Далее, осталось только выделить среди них те, что имеют аннотацию @Component :
Запустите и проверьте. В консоли должно быть что-то вроде этого:
Теперь убедитесь в том, что всё работает. Контейнер должен создавать бины, и они должны извлекаться по имени. Обратите внимание на то, что название вашего метода instantiate() и название метода classObject.newInstance(); имеют общий корень. Более того, instantiate() — это часть жизненного цикла бина. В джаве всё взаимосвязано!
Наполняем свойства
Далее, добавим её к свойству:
Теперь нам нужно научить наш BeanFactory находить эти аннотации и инжектировать зависимости по ним. Добавим отдельный метод для этого, и вызовем его из Main :
Далее, нам надо ещё разок пройтись по всем бинам и посмотреть их тип — вдруг это тот тип, который хочет себе взять наш бин. Да, у нас получается трехмерный цикл!
Теперь запустите ваш проект и убедитесь, что при вызове productService.getPromotionsService() вместо null возвращается наш бин.
Поддерживаем бины, знающие о своем имени
Бывают случаи, когда внутри бина нужно получить его имя. Такая потребность возникает не часто, т.к. бины, по своей сути, не должны знать друг о друге и о том, что они бины. В первых версиях спринга предполагалось, что бин — это POJO (Plain Old Java Objec, старый добрый Джава-объект), а вся конфигурация вынесена в XML-файлы и отделена от реализации. Но мы реализуем данный функционал, так как инъекция имени — это часть жизненного цикла бина.
Как нам узнать, какой бин хочет узнать, как его зовут, а какой не хочет? Первое, что приходит в голову — это сделать новую аннотацию типа @InjectName и лепить её на поля типа String. Но это решение будет слишком общим и позволяет выстрелить себе в ногу много раз (разместить эту аннотацию на полях неподходящих типов (не String), или же пытаться инжектировать имя в несколько полей в одном классе). Есть другое решение, более аккуратное — создать специальный интерфейс с одним методом-сеттером. Все бины, что его реализуют — получает своё имя. Создайте класс BeanNameAware в пакете org.springframework.beans.factory :
Далее, пускай наш PromotionsService его реализует:
И, наконец, добавим новый метод в фабрику бинов. Тут всё просто — мы проходимся по нашим бинам-синглтонам, проверяем, реализует ли бин наш интерфейс, и вызываем сеттер:
Запустите и убедиесь, что всё работает:
Надо отметить, что в спринге есть и другие подобные интерфейсы. Я рекомендую вам самостоятельно реализовать интерфейс BeanFactoryAware, который позволит бинам получать ссылку на фабрику бинов. Реализуется он аналогично.
Инициализируем бины
Добавляем пост-процессоры
Представьте себя на месте первых разработчиков спринга. Ваш фреймворк растёт и пользуется огромной популярностью среди девелоперов, на почту каждый день приходят письма с просьбами добавить ту или иную полезную фичу. Если для каждой такой фичи добавлять свой собственный интерфейс и проверять его в жизенном цикле бина, то он (жизненный цикл) окажется засоренным ненужной информацией. Вместо этого, мы можем создать один универсальный интерфейс, который позволит добавлять выполнение некоторой логики (абсолютно любой, будь то проверка на аннотацию, замена бина на другой бин, установка некоторых особых свойств и так далее).
Давайте подумаем, для чего предназначен данный интерфейс. Он должен производить некоторую пост-обработку бинов, следовательно его можно назвать BeanPostProcessor. Но перед нами стоит непростой вопрос — когда следует выполнять логику? Ведь мы можем выполнить её до инициализации, а можем выполнить и после. Для одних задач лучше подходит первый вариант, для других — второй… Как быть?
Мы можем позволить оба варианта сразу. Пускай один пост-процессор несёт две логики, два метода. Один выполняется до инициализации (до метода afterPropertiesSet() ), а другой — после. Теперь давайте задумаемся над самими методами — какие параметры у них должны быть? Очевидно, что там должен быть сам бин ( Object bean ). Для удобства, кроме бина можно передавать имя этого бина. Вы же помните, что бин сам по себе не знает о своём имени. И мы не хотим заставлять все бины реализовывать интерфейс BeanNameAware. Но, на уровне пост-процессора, имя бина может очень даже пригодиться. Поэтом удобавляем его как второй параметр.
А что должен возвращать метод при пост-обработке бина? Сделаем так, чтобы он возвращал сам бин. Это даёт нам супер-гибкость, ведь вместо бина можно подсунуть прокси-объект, который оборачивает его вызовы (и добавляет секьюрити). А можно и вовсе вернуть другой объект, пересоздав бин заново. Разработчикам даётся очень большая свобода действия. Ниже представлена окончательная версия спроектированного интерфейса:
Далее, нам нужно добавить список из прост-процессоров к нашей фабрике бинов и возможность добавлять новые. Да, это обычный ArrayList.
Теперь поменяем метод initializeBeans так, чтобы он учитывал пост-процессоры:
Давайте создадим небольшой пост-процессор, который просто трассирует вызовы в консоль, и добавим его в нашу фабрику бинов:
Обязательно создавайте все аннотации и пакеты (даже javax.annotation) вручную, не подключайте зависимости! Это поможет вам увидеть разницу между ядром спринга и его расширениями (поддержка javax), и запомнить её. Это позволит придерживаться одного стиля в будущем.
Вам будет интересен тот факт, что в реальном спринге аннотация @PostConstruct именно так и реализована, через пост-процессор CommonAnnotationBeanPostProcessor. Но не подглядывайте туда, напишите свою реализацию.
Полный жизненный цикл бина
Вот мы и реализовали полный жизненный цикл бина, в его современном виде. Я надеюсь, что такой подход поможет вам его запомнить.
Наш любимый контекст
Статья получилась слишком большой и содержимое стало обрезаться, поэтому я поместил информацию по контексту под спойлер.
На самом деле, информация о типах сохраняется в метаданных класса, и очень даже успешно извлекается через рефлексию. Для начала, нам нужно получить список из интерфейсов, которые данный бин реализует, и отфильтровать среди них те, которые имеют параметры:
Далее, мы всего лишь извлекаем тип первого параметра, и убеждаемся, что он — наш класс события. Если это так, мы получаем наш метод через рефлексию и вызываем его:
Пускай один из ваших классов реализует интерфейс ApplicationListener:
Далее, тестируете ваш контекст в Main и убеждаетесь, что он также работает, и событие отправляется:
Заключение
Изначально я планировал данную статью для Baeldung на английском, но потом подумал, что аудитория хабры может положительно оценить данный подход к обучению. Если вам понравились мои идеи, обязательно поддержите статью. Если она наберёт рейтинг более 30, то обещаю продолжение. При написании статьи, я старался показать именно те знания Spring Core, которе используются наиболее часто, а также с опорой на Core Spring 5.0 Certification Study Guide. В будущем, с помощью таких туториалов можно покрыть всю сертификацию и сделать спринг более доступным для Java-разработчиков.
Update 10/05/2018
Мне постоянно приходят письма с вопросами «а когда продолжение, мы его ждём». Но вот времени совсем нету, и другие личные проекты в приоритете. Однако, если кому-то из вас действительно понравилась идея, вы можете изучить узкий раздел спринга и написать статью-продолжение. Если у вас нету аккаунта хабры, то я могу опубликовать статью от моего акка или помочь вам получить инвайт.
Распределение тем:
Spring Container — [имя пользователя]
Spring AOP — [имя пользователя]
Spring Web — [имя пользователя]
Spring Cloud — [имя пользователя]