Перейти к основному содержимому

Руководство по запуску логики при запуске в Spring

· 5 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

В этом руководстве мы сосредоточимся на том, как запускать логику при запуске приложения Spring.

2. Запуск логики при запуске

Запуск логики во время/после запуска приложения Spring является распространенным сценарием. Но это также тот, который вызывает множество проблем.

Чтобы извлечь выгоду из Inverse of Control, нам нужно отказаться от частичного контроля над потоком приложения в контейнер. Вот почему создание экземпляров, логика настройки при запуске и т. д. требуют особого внимания.

Мы не можем просто включить нашу логику в конструкторы bean-компонентов или вызвать методы после создания экземпляра любого объекта, потому что мы не контролируем эти процессы.

Давайте посмотрим на реальный пример:

@Component
public class InvalidInitExampleBean {

@Autowired
private Environment env;

public InvalidInitExampleBean() {
env.getActiveProfiles();
}
}

Здесь мы пытаемся получить доступ к автосвязанному полю в конструкторе. Когда вызывается конструктор, bean-компонент Spring еще не полностью инициализирован. Это проблема, потому что вызов полей, которые еще не инициализированы, приведет к NullPointerException s.

Давайте рассмотрим несколько способов, которыми Spring предлагает нам справиться с этой ситуацией.

2.1. Аннотация @PostConstruct _

Мы можем использовать аннотацию Javax @PostConstruct для аннотации метода, который должен быть запущен один раз сразу после инициализации компонента. Имейте в виду, что Spring запустит аннотированный метод, даже если нечего вводить.

Вот @PostConstruct в действии:

@Component
public class PostConstructExampleBean {

private static final Logger LOG
= Logger.getLogger(PostConstructExampleBean.class);

@Autowired
private Environment environment;

@PostConstruct
public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

Мы видим, что экземпляр Environment был безопасно внедрен, а затем вызван в аннотированном методе @PostConstruct без создания исключения NullPointerException .

2.2. Интерфейс InitializingBean _

Подход InitializingBean работает аналогичным образом. Вместо аннотирования метода нам нужно реализовать интерфейс InitializingBean и метод afterPropertiesSet() .

Здесь мы реализуем предыдущий пример, используя интерфейс InitializingBean :

@Component
public class InitializingBeanExampleBean implements InitializingBean {

private static final Logger LOG
= Logger.getLogger(InitializingBeanExampleBean.class);

@Autowired
private Environment environment;

@Override
public void afterPropertiesSet() throws Exception {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

2.3. ApplicationListener _ ``

Мы можем использовать этот подход для запуска логики после инициализации контекста Spring. Итак, мы не фокусируемся на каком-то конкретном бобе. Вместо этого мы ждем, пока все они инициализируются.

Для этого нам нужно создать bean-компонент, реализующий интерфейс ApplicationListener<ContextRefreshedEvent> :

@Component
public class StartupApplicationListenerExample implements
ApplicationListener<ContextRefreshedEvent> {

private static final Logger LOG
= Logger.getLogger(StartupApplicationListenerExample.class);

public static int counter;

@Override public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Increment counter");
counter++;
}
}

Мы можем получить те же результаты, используя недавно введенную аннотацию @EventListener :

@Component
public class EventListenerExampleBean {

private static final Logger LOG
= Logger.getLogger(EventListenerExampleBean.class);

public static int counter;

@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Increment counter");
counter++;
}
}

Мы хотим убедиться, что выбрали подходящее событие для наших нужд. В этом примере мы выбрали ContextRefreshedEvent .

2.4. Атрибут @Bean initMethod _

Мы можем использовать свойство initMethod для запуска метода после инициализации компонента.

Вот как выглядит фасоль:

public class InitMethodExampleBean {

private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);

@Autowired
private Environment environment;

public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

Обратите внимание, что мы не реализовали никаких специальных интерфейсов и не использовали никаких специальных аннотаций.

Затем мы можем определить компонент с помощью аннотации @Bean :

@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
return new InitMethodExampleBean();
}

А вот как определение bean-компонента выглядит в XML-конфигурации:

<bean id="initMethodExampleBean"
class="com.foreach.startup.InitMethodExampleBean"
init-method="init">
</bean>

2.5. Внедрение конструктора

Если мы внедряем поля с помощью Constructor Injection, мы можем просто включить нашу логику в конструктор:

@Component 
public class LogicInConstructorExampleBean {

private static final Logger LOG
= Logger.getLogger(LogicInConstructorExampleBean.class);

private final Environment environment;

@Autowired
public LogicInConstructorExampleBean(Environment environment) {
this.environment = environment;
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

2.6. Spring Boot CommandLineRunner

Spring Boot предоставляет интерфейс CommandLineRunner с методом обратного вызова run() . Это можно вызвать при запуске приложения после создания экземпляра контекста приложения Spring.

Давайте посмотрим на пример:

@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
private static final Logger LOG =
LoggerFactory.getLogger(CommandLineAppStartupRunner.class);

public static int counter;

@Override
public void run(String...args) throws Exception {
LOG.info("Increment counter");
counter++;
}
}

Примечание . Как упоминалось в документации , несколько bean-компонентов CommandLineRunner могут быть определены в одном контексте приложения и могут быть упорядочены с помощью интерфейса @Ordered или аннотации @Order .

2.7. Spring Boot ApplicationRunner

Подобно CommandLineRunner , Spring Boot также предоставляет интерфейс ApplicationRunner с методом run() , который вызывается при запуске приложения. Однако вместо необработанных строковых аргументов, передаваемых в метод обратного вызова, у нас есть экземпляр класса ApplicationArguments .

Интерфейс ApplicationArguments имеет методы для получения значений аргументов, которые являются параметрами и значениями простых аргументов. Аргумент с префиксом – – является опциональным аргументом.

Давайте посмотрим на пример:

@Component
public class AppStartupRunner implements ApplicationRunner {
private static final Logger LOG =
LoggerFactory.getLogger(AppStartupRunner.class);

public static int counter;

@Override
public void run(ApplicationArguments args) throws Exception {
LOG.info("Application started with option names : {}",
args.getOptionNames());
LOG.info("Increment counter");
counter++;
}
}

3. Объединение механизмов

Чтобы иметь полный контроль над нашими bean-компонентами, мы могли бы объединить вышеперечисленные механизмы вместе.

Это порядок выполнения:

  1. constructor
  2. `Аннотированные методы @PostConstruct`
  3. Метод InitializingBean afterPropertiesSet()
  4. метод инициализации, указанный как init-method в XML

Создадим bean-компонент Spring, объединяющий все механизмы:

@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {

private static final Logger LOG
= Logger.getLogger(AllStrategiesExampleBean.class);

public AllStrategiesExampleBean() {
LOG.info("Constructor");
}

@Override
public void afterPropertiesSet() throws Exception {
LOG.info("InitializingBean");
}

@PostConstruct
public void postConstruct() {
LOG.info("PostConstruct");
}

public void init() {
LOG.info("init-method");
}
}

Если мы попытаемся создать экземпляр этого компонента, мы увидим журналы, соответствующие указанному выше порядку:

[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method

4. Вывод

В этой статье мы показали несколько способов запуска логики при запуске приложения Spring.

Примеры кода можно найти на GitHub .