При разработке на Spring Boot иногда нам нужно выполнить метод или фрагмент кода при запуске приложения. Этот код может быть любым, от записи определенной информации до настройки базы данных, заданий cron и т. д. Мы не можем просто поместить этот код в конструктор, потому что требуемые переменные или службы могут быть еще не инициализированы. Это может привести к null pointer исключению или некоторым другим.
Зачем нам нужно запускать код при запуске Spring Boot?
Нам нужно запускать метод при запуске приложения по многим причинам, например, для:
Записи важных событий или сообщения о запуске приложения
Обработки базы данных или файлов, индексации, создания кэшей и т. д.
Запуска фоновых процессов, таких как отправка уведомления, выборка данных из некоторой очереди и т. д.
Различные способы запуска метода после запуска Spring Boot
У каждого способа есть свои преимущества. Давайте рассмотрим подробнее, чтобы решить, что нам следует использовать:
интерфейс CommandLineRunner
интерфейс ApplicationRunner
события Spring Boot Application
аннотацию @Postconstruct для метода
интерфейс InitializingBean
атрибут инициализации аннотации @bean
1. Использование интерфейса CommandLineRunner
CommandLineRunner - это функциональный интерфейс Spring Boot, который используется для запуска кода при запуске приложения. Он находится в пакете org.springframework.boot.
В процессе запуска после инициализации контекста Spring boot вызывает свой метод run() с аргументами командной строки, предоставленными приложению.
Чтобы сообщить Spring Boot о нашем интерфейсе commandlineRunner, мы можем либо реализовать его и добавить аннотацию @Component над классом, либо создать его bean-компонент с помощью @bean.
Пример реализации интерфейса CommandLineRunner
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("In CommandLineRunnerImpl ");
for (String arg : args) {
System.out.println(arg);
}
}
}
Пример создания bean-компонента интерфейса CommandLineRunner
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Bean
public CommandLineRunner CommandLineRunnerBean() {
return (args) -> {
System.out.println("In CommandLineRunnerImpl ");
for (String arg : args) {
System.out.println(arg);
}
};
}
}
Мы можем запустить приложение из командной строки или IDE. Давайте рассмотрим пример, когда мы запускаем приложение с аргументами «–status = running».
mvn spring-boot:run -Dspring-boot.run.arguments="--status=running"
ИЛИ
mvn package
java -jar target/<FILENAME.JAR HERE> --status=running
Это дает следующий вывод журнала:
In CommandLineRunnerImpl
status=running
Как мы видим, параметр не анализируется, а вместо этого интерпретируется как одно значение «status = running».
Чтобы получить доступ к аргументам командной строки в проанализированном формате, нам нужно использовать интерфейс ApplicationRunner. Мы рассмотрим это чуть позже.
Spring Boot добавляет интерфейс CommandLineRunner в процесс запуска. Следовательно, выброшенное исключение в commandlineRunner заставит процесс загрузки Spring прервать запуск.
Мы можем создать несколько CommandLineRunner в одном приложении. Используя интерфейс Ordered или аннотацию @Order, мы можем настроить порядок, в котором они должны запускаться. Меньшее значение означает более высокий приоритет. По умолчанию все компоненты создаются с самым низким приоритетом. Поэтому компоненты не указанные конфигурации order будут вызываться последними.
Мы можем использовать аннотацию @Order, как показано ниже
@Component
@Order(1)
public class CommandLineRunnerImpl implements CommandLineRunner {
........
}
2. Использование интерфейса ApplicationRunner
Как обсуждалось ранее, для доступа к анализируемым аргументам нам необходимо использовать интерфейс ApplicationRunner. Интерфейс ApplicationRunner предоставляет метод запуска с ApplicationArguments вместо необработанного массива строк.
ApplicationArguments - это интерфейс, который доступен из srping boot 1.3 в пакете org.springframework.boot.
Он предоставляет различные способы доступа к аргументам, как показано ниже.
String[] GetSourceArgs() |
Предоставляет необработанные аргументы, которые были переданы приложению |
Set<String> getOptionNames() |
Именам всех необязательных аргументов предшествует «-», например: –name = «stacktrace» |
List<String> getNonOptionArgs() |
Возвращает необработанные необязательные аргументы. Аргументы без «-» |
boolean containsOption(String name) |
Проверяет, присутствует ли имя в необязательных аргументах или нет |
List<String> getOptionValues(String name) |
Предоставляет значение аргумента по имени |
Метод getOptionValues возвращает список значений, потому что значение аргумента может быть массивом, поскольку мы можем использовать один и тот же ключ более одного раза в командной строке.
Например, –name = “stacktrace” - Port = 8080 –name = ”guru”.
Пример реализации интерфейса Application runner
Давайте запустим приведенную ниже программу, используя аргументы «status = running –mood = happy 10–20», и давайте разберемся с результатом.
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunnerImpl Called");
//print all arguemnts: arg: status=running, arg: --mood=happy, 10, --20
for (String arg : args.getSourceArgs()) {
System.out.println("arg: "+arg);
}
System.out.println("NonOptionArgs: "+args.getNonOptionArgs()); //[status=running,10]
System.out.println("OptionNames: "+args.getOptionNames()); //[mood, 20]
System.out.println("Printing key and value in loop:");
for (String key : args.getOptionNames()) {
System.out.println("key: "+key); //key: mood //key: 20
System.out.println("val: "+args.getOptionValues(key)); //val:[happy] //val:[]
}
}
}
Вывод:
ApplicationRunnerImpl Called
arg: status=running
arg: --mood=happ
arg: 10
arg: --20
NonOptionArgs: [status=running , 10]
OptionNames: [mood, 20]
Printing key and value in loop:
key: mood
val: [happy]
key: 20
val: []
CommandLineRunner и ApplicationRunner имеют схожие функции, например:
Исключение в методе run() прервет запуск приложения.
Несколько ApplicationRunner можно заказать с помощью упорядоченного интерфейса или аннотации @Order.
Важнее всего отметить, что порядок разделяется между CommandLineRunners и ApplicationRunners. Это означает, что порядок выполнения может быть смешанным между commandlineRunner и applicationRunner.
3. Событие приложения в Spring Boot
Фреймворк Spring запускает разные события в разных ситуациях. Он также вызывает множество событий в процессе запуска. Мы можем использовать эти события для выполнения нашего кода, например, ApplicationReadyEvent можно использовать для выполнения кода после запуска приложения загрузки Spring.
Если нам не нужны аргументы командной строки, это лучший способ выполнить код после запуска приложения.
@Component
public class RunAfterStartup{
@EventListener(ApplicationReadyEvent.class)
public void runAfterStartup() {
System.out.println("Yaaah, I am running........");
}
Некоторые наиболее важные события spring boot,
ApplicationContextInitializedEvent: запускается после подготовки ApplicationContext и вызова ApplicationContextInitializers, но до загрузки определений bean-компонентов
ApplicationPreparedEvent: запускается после загрузки определений bean-компонентов
ApplicationStartedEvent: запускается после обновления контекста, но до вызова командной строки и запуска приложений.
ApplicationReadyEvent: запускается после вызова любого приложения и запуска командной строки
ApplicationFailedEvent: срабатывает, если есть исключение при запуске
Можно создать несколько ApplicationListeners. Их можно упорядочивать с помощью аннотации @Order или с помощью интерфейса Ordered.
Order используется совместно с другими ApplicationListeners того же типа, но не с ApplicationRunners или CommandLineRunners.
4. Аннотация метода @Postconstruct
Метод можно пометить аннотацией @PostConstruct. Всякий раз, когда метод отмечен этой аннотацией, он будет вызываться сразу после внедрения зависимости.
Метод @PostConstruct связан с конкретным классом, поэтому его следует использовать только для кода конкретного класса. В каждом классе может быть только один метод с аннотацией postConstruct.
@Component
public class PostContructImpl {
public PostContructImpl() {
System.out.println("PostContructImpl Constructor called");
}
@PostConstruct
public void runAfterObjectCreated() {
System.out.println("PostContruct method called");
}
}
Вывод:
PostContructImpl Constructor called
postContruct method called
Следует отметить, что, если класс помечен как 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.
@Component
public class InitializingBeanImpl implements InitializingBean {
public InitializingBeanImpl() {
System.out.println("InitializingBeanImpl Constructor called");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBeanImpl afterPropertiesSet method called");
}
}
Вы можете подумать, что же произойдет, если мы будем использовать одновременно аннотацию @PostConstruct и InitializingBean. В этом случае метод @PostConstruct будет вызываться перед методом afterPropertiesSet() InitializingBean.
6. Атрибут Init аннотации @bean
Мы можем реализовать метод, используя свойство initMethod в аннотации @Bean. Этот метод будет вызываться после инициализации bean-компонента.
Метод, предоставленный в initMethod, должен быть void и не иметь аргументов. Этот метод может быть даже private.
public class BeanInitMethodImpl {
public void runAfterObjectCreated() {
System.out.println("yooooooooo......... someone called me");
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean(initMethod="runAfterObjectCreated")
public BeanInitMethodImpl getFunnyBean() {
return new BeanInitMethodImpl();
}
}
Вывод:
yooooooooo......... someone called me
Если у вас есть реализация InitializingBean и свойство initMethod аннотации @Bean для того же класса, то метод afterPropertiesSet для InitializingBean будет вызываться перед initMethod.
Сочетание разных подходов
Наконец, иногда нам может потребоваться объединить несколько вариантов. При этом они будут выполняться в следующем порядке:
Конструктор
PostContruct метод
afterPropertiesSet метод
Метод инициализации Bean-компонента
ApplicationStartedEvent
ApplicationRunner или CommandLineRunner в зависимости от порядка
ApplicationReadyEvent
Краткий обзор
Есть разные способы запустить код после запуска Spring Boot приложения
Мы можем использовать CommandLineRunner или ApplicationRunner Interface.
Используйте интерфейс ApplicationRunner для доступа к анализируемым аргументам вместо массива необработанных строк
Событие загрузки Spring выполняет код при запуске приложения
Метод, отмеченный аннотацией @PostConstruct, выполняется после инициализации объекта
Метод afterPropertiesSet() интерфейса InitializingBean вызывается после инициализации объекта.
В аннотации @Bean есть атрибут «initMethod» для предоставления метода, который будет вызываться после инициализации bean-компонента.
Связанные темы
Комментарии (7)
LaRN
31.08.2021 09:58В последнем варианте не описано где должен находиться класс BeanInitMethodImpl.
В том же пакете что DemoApplication и или иметь какую-то аннотацию? Не ясно как Spring будет искать метод (по имени) который нужно выполнить и что будет если несколько классов будут иметь метод с таким именем. Будет ли вызвано исключение, если среди найденных по имени методов часть будут иметь входные параметры или тип выходного значения отличный от void. Ну или все неподходящий по сигнатуре методы проигнорируются.
Ещё интересный момент, если метод будет бросать проверяемое исключение, он сможет бы использован на предзагрузке?
aleksandy
31.08.2021 10:40Написано же,
Метод, предоставленный в initMethod, должен быть void и не иметь аргументов. Этот метод может быть даже private.
initMethod - это метод создаваемого бина. Де-факто, то же самое, что и @PostConstruct, только через механизмы spring, а не стандартные java-овые.
LaRN
31.08.2021 10:55Да, я это все прочитал. Вопрос, что будет если нарушить правила :)
Просто упадет с ошибкой, просто проигнорирует неподходящие методы, выдаст при старте в лог диагностику или еще что-то.
Zippospb
31.08.2021 15:00Когда нам нужно было проделать манипуляции в БД с поддержкой транзакций, но до полной загрузки приложения, мы использовали интерфейс SmartInitializingSingleton.
При вызове метода, помеченного аннотацией @PostConstruct , транзакции еще не работают
vasia-punki-13
31.08.2021 15:00Если нужна конфигурация бина то можно так
@SpringBootConfiguration public class Configuration { ... ... @Autowired public void registerCustomConverters(FormatterRegistry registry) { registry.addConverter(new StringToRangeHeaderConverter()); } }
Serge1001
31.08.2021 15:01К модификатору доступа у метода с аннотацией @PostConstruct есть вопрос: а не должен ли он быть private?
Не нарушением ли мы инкапсуляцию, добавляя метод как часть контракта?
iiwabor
У меня как раз недавно такая задача была - по окончанию загрузки Spring Boot надо было выполнить некоторые действия.
С @PostConstructне прокатило - пока это выполнялось - приложение не отвечало
А с @EventListener(ApplicationReadyEvent.class)все ОК