Джерело зображення
Spring Boot надає практичний спосіб роботи з архітектурами, орієнтованими на події, спрощуючи управління життєвим циклом корпоративних застосунків. Одним з важливих аспектів цієї функціональності є обробка подій контексту застосунку. Ці події, такі як ContextRefreshedEvent
, ContextStartedEvent
і ContextClosedEvent
, дозволяють розробникам інтегрувати конкретні задачі на різних етапах життєвого циклу Spring-застосунку.
Ця стаття пояснює, як ці події публікуються та обробляються в Spring Boot застосунку, з акцентом на технічні деталі їх реалізації.
Огляд подій контексту застосунку
Події, орієнтовані на події в Spring, надають механізм для взаємодії бінів під час життєвого циклу застосунку. Події контексту застосунку є спеціальними подіями, які ініціюються контейнером Spring для сигналізації про зміни стану чи досягнуті етапи.
Що таке події контексту застосунку?
Події контексту застосунку є підмножиною системи подій Spring. Ці події публікуються автоматично фреймворком Spring, коли відбуваються певні етапи життєвого циклу контексту застосунку. До найбільш поширених подій належать:
ContextRefreshedEvent
: Публікується, коли контекст застосунку оновлюється або ініціалізується.ContextStartedEvent
: Публікується, коли викликається методstart()
на контексті.ContextClosedEvent
: Публікується, коли контекст застосунку закривається.ContextStoppedEvent
: Публікується, коли викликається методstop()
на контексті.
Ці події надають хук для розробників, щоб виконати код під час певних фаз життєвого циклу застосунку.
Механізм публікації подій
Публікація подій у Spring Boot є основним механізмом фреймворку, призначеним для полегшення взаємодії між бінами в контексті застосунку. Процес включає в себе різні компоненти, які працюють разом, включаючи ApplicationContext
, ApplicationEventPublisher
та прослуховувачі подій. Давайте розглянемо, як цей механізм працює крок за кроком.
Ініціація подій
Публікація події починається, коли відбувається певна дія або зміна життєвого циклу в контейнері Spring. Наприклад:
- Коли контекст застосунку оновлюється, ініціюється подія
ContextRefreshedEvent
. - Коли контекст закривається, ініціюється подія
ContextClosedEvent
.
На цьому етапі контейнер Spring створює екземпляр відповідного класу події. Ці класи подій розширюють клас ApplicationEvent
, який є базовим класом для всіх подій застосунку.
public abstract class ApplicationEvent extends EventObject {
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
Тут:
source
представляє об'єкт, який ініціював подію.timestamp
записує час події.
Публікація подій
Інтерфейс ApplicationEventPublisher
відповідає за розповсюдження подій до всіх зацікавлених прослуховувачів. Цей інтерфейс реалізується класом AbstractApplicationContext
, який є базовим класом для більшості реалізацій контексту застосунку.
Ключовий метод в ApplicationEventPublisher
:
void publishEvent(Object event);
Коли подія публікується, метод publishEvent
обгортає подію в загальний PayloadApplicationEvent
, тільки якщо це не є вже ApplicationEvent
і функція публікації події з навантаженням увімкнена. Це дозволяє використовувати об'єкти, які не є ApplicationEvent
, як події.
Подія потім передається до ApplicationEventMulticaster
для розповсюдження до зареєстрованих прослуховувачів.
Мультикастинг подій
Розповсюдження подій насправді здійснюється через ApplicationEventMulticaster
, який є абстракцією, призначеною для керування розповсюдженням подій до прослуховувачів.
За умовчанням, реалізація, яку використовує Spring, — це SimpleApplicationEventMulticaster
.
Процес у SimpleApplicationEventMulticaster
:
- Отримання слухачів: Мультикастер отримує всіх слухачів, які можуть отримати подію, залежно від їх оголошеного інтересу до типу події.
- Асинхронне або синхронне виконання: За умовчанням мультикастер викликає слухачів синхронно. Однак можна налаштувати його для використання
Executor
, щоб виконувати слухачів асинхронно. - Обробка помилок: Якщо слухач викидає виключення, мультикастер перехоплює і реєструє його, щоб не переривати роботу інших слухачів. Розробники можуть перевизначити метод
handleListenerException
у власномуApplicationEventMulticaster
, щоб додати власну логіку обробки помилок.
Відрізок з SimpleApplicationEventMulticaster
:
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
for (ApplicationListener listener : getApplicationListeners(event, eventType)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
Тут:
getApplicationListeners(event, eventType)
отримує відповідних слухачів.- Якщо доступний
Executor
, він використовується для асинхронного виконання.
Визначення слухачів
Отримання слухачів для певної події є важливим етапом процесу. Інтерфейс ApplicationListener
визначає контракт для бінів, які хочуть слухати певні події. Слухачі реєструються в контексті під час запуску та зберігаються в ефективній структурі даних для швидкого пошуку.
Слухачі фільтруються за:
- Типом події: Слухач повинен бути зареєстрований для певного типу події, яка публікується, або для одного з її батьківських класів.
- Джерелом події: Якщо слухач прив'язаний до джерела, джерело події має відповідати вимогам слухача.
Приклад фільтрації:
protected Collection> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
return this.applicationListeners.stream()
.filter(listener -> supportsEvent(listener, event, eventType))
.collect(Collectors.toList());
}
Розповсюдження подій
Якщо відповідні слухачі ідентифіковані, подія передається кожному слухачу по черзі. Це передбачає виклик методу onApplicationEvent
для кожного слухача з передачею об'єкта події.
Основні етапи:
- Розповсюдження події: Метод
onApplicationEvent
викликається на слухачі з об'єктом події. - Контекст виконання: Якщо виконання слухача синхронне, подія обробляється в поточному потоці. Якщо асинхронне, обробка відбувається в окремому потоці, яким керує налаштований
Executor
.
Приклад:
public void invokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
} catch (Throwable ex) {
// Логування або обробка виключення
}
}
Цей модульний дизайн дозволяє розробникам налаштовувати або перевизначати частини механізму обробки подій без впливу на інші компоненти.
Підтримка власних подій
Окрім попередньо визначених подій, таких як ContextRefreshedEvent
, розробники можуть визначати та публікувати власні події.
Ці власні події слідують тій самій життєвій циклі та обробляються за допомогою тих самих механізмів.
Приклад власної події:
public class CustomEvent extends ApplicationEvent {
private final String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
Публікація власної події:
@Component
public class CustomEventPublisher {
private final ApplicationEventPublisher eventPublisher;
public CustomEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void publishEvent(String message) {
CustomEvent customEvent = new CustomEvent(this, message);
eventPublisher.publishEvent(customEvent);
}
}
Обробка подій контексту застосунку
Обробка подій контексту застосунку в Spring Boot керується за допомогою поєднання слухачів, анотацій і поведінки під час виконання, визначеної фреймворком. Ці механізми дозволяють розробникам виконувати власну логіку на певних етапах життєвого циклу застосунку. Основна увага приділена тому, як слухачі реєструються, викликаються та оптимізуються для покращення продуктивності під час обробки подій.
Інтерфейс ApplicationListener
Інтерфейс ApplicationListener
є основним контрактом для обробки подій у Spring. Реалізація цього інтерфейсу дозволяє біну реагувати на конкретний тип події.
Деталі реалізації:
- Зв'язування з типом: Інтерфейс використовує параметр типу для прив'язки слухача до конкретного типу події. Це дозволяє уникнути перевірки типу під час виконання та покращує ефективність розповсюдження подій.
- Фільтрація подій: Лише події, що відповідають вказаному типу (або його підкласам), будуть розповсюджені до слухача.
Приклад:
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.stereotype.Component;
@Component
public class ContextStartedListener implements ApplicationListener {
@Override
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("Контекст запущено: " + event.getApplicationContext().getId());
}
}
Тут:
- Слухач явно прив'язаний до
ContextStartedEvent
. - Він автоматично реєструється в контексті застосунку, коли оголошений як Spring-бін.
Обробка подій за допомогою @EventListener
Анотація @EventListener
надає декларативний спосіб обробляти події без реалізації інтерфейсу ApplicationListener
. Цей метод більш гнучкий і дозволяє розробникам визначати додаткові умови для обробки подій.
Основні механізми:
- Визначення типу події: Параметр методу визначає тип події, яку він оброблятиме.
- Умови SpEL (Spring Expression Language): Логіка умов може бути застосована за допомогою атрибута
condition
для фільтрації подій під час виконання. - Асинхронне виконання: Додавши
@Async
до методу@EventListener
, подія буде оброблена асинхронно, якщо налаштованоExecutor
.
Приклад із умовним слухачем:
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class CustomEventListener {
@EventListener(condition = "#event.message == 'START'")
public void handleCustomEvent(CustomEvent event) {
System.out.println("Отримано власну подію: " + event.getMessage());
}
}
У цьому прикладі:
- Слухач реагує тільки на події, де поле
message
дорівнює"START"
. - Атрибут
condition
динамічно фільтрує події перед викликом методу.
Порядок обробки подій
Spring Boot надає опції для визначення порядку виконання слухачів. Це корисно, коли кілька слухачів обробляють одну й ту ж подію і їх потрібно виконати в певній послідовності.
Механізми:
1.
Анотація @Order: Анотація @Order
може бути застосована до методів @EventListener
або Spring-бінів, що реалізують ApplicationListener
, для вказівки пріоритету виконання. Нижчі значення вказують на вищий пріоритет.
2. Інтерфейс Ordered: Для класів, що реалізують ApplicationListener
, можна використовувати інтерфейс Ordered
для визначення пріоритету програмно.
Приклад:
import org.springframework.core.annotation.Order;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class OrderedEventListener {
@EventListener
@Order(1)
public void firstListener(ContextRefreshedEvent event) {
System.out.println("Перший слухач виконано");
}
@EventListener
@Order(2)
public void secondListener(ContextRefreshedEvent event) {
System.out.println("Другий слухач виконано");
}
}
Тут:
- Метод
firstListener
виконується перед методомsecondListener
, коли публікується подіяContextRefreshedEvent
.
Пропагування подій та ізоляція слухачів
Під час обробки подій процес пропагування гарантує, що виключення в одному слухачі не впливатимуть на інші. Spring ізолює слухачів за допомогою блоку try-catch навколо кожного виклику слухача.
Фрагмент з SimpleApplicationEventMulticaster
:
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
} catch (Throwable ex) {
handleListenerException(ex, event);
}
}
Основні моменти:
- Кожен слухач працює незалежно, і будь-яке виключення, яке виникає, фіксується в журналі, але не перериває потік обробки подій.
- Користувацьку обробку помилок можна налаштувати, переопрацювавши метод
handleListenerException
у власному мультикастере подій.
Потокове виконання та асинхронні слухачі
За замовчуванням Spring обробляє події синхронно в потоці публікації. Однак для подій, що потребують обробки у фоновому режимі, слухачі можуть бути позначені як асинхронні.
Налаштування:
- Створіть бін
Executor
для асинхронного виконання:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("AsyncExecutor-");
executor.initialize();
return executor;
}
}
Позначте метод слухача анотацією @Async
:
import org.springframework.scheduling.annotation.Async;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class AsyncEventListener {
@Async
@EventListener
public void handleAsyncEvent(ContextStartedEvent event) {
System.out.println("Асинхронне оброблення ContextStartedEvent");
}
}
Деталі виконання:
- Подія передається на виконання до
executor
для обробки. - Це дозволяє основному потоку продовжувати роботу без блокування на час обробки слухачем.
Оптимізація продуктивності слухачів
Для додатків з великою кількістю слухачів або частим публікуванням подій продуктивність обробки подій може стати проблемою.
Spring пропонує стратегії для вирішення цієї проблеми.
- Вибіркова реєстрація слухачів: Використовуйте умовні визначення бінів для реєстрації слухачів тільки за певних конфігурацій.
Приклад:
// Переконайтеся, що залежність spring-boot-starter (або spring-boot-autoconfigure) включена
@Component
@ConditionalOnProperty(name = "event.listener.enabled", havingValue = "true")
public class ConditionalEventListener {
@EventListener
public void handleEvent(CustomEvent event) {
// Логіка тут
}
}
- Фільтрація подій під час реєстрації: Замість фільтрації під час виконання, реєструйте слухачів тільки для тих подій, які їх цікавлять, використовуючи конфігурації в межах певної області.
- Асинхронне розповсюдження подій: Налаштуйте пул потоків для обробки подій паралельно.
Висновок
Механізм подій контексту додатка в Spring Boot надає структурований спосіб керувати та реагувати на зміни життєвого циклу додатка. Публікуючи події, такі як ContextRefreshedEvent
, ContextStartedEvent
і ContextClosedEvent
, фреймворк пропонує безперешкодний канал для комунікації між бінами. Механізм складається з чітких кроків — від створення і публікації подій до виклику слухачів та їх поширення.
Слухачі, зареєстровані через інтерфейси або анотації, працюють з гнучкістю та можуть обробляти події синхронно або асинхронно. Модульний дизайн мультикастингу подій дозволяє слухачам працювати незалежно, зберігаючи стабільність системи під час обробки подій. Завдяки таким можливостям, як умовна реєстрація, впорядковане виконання та асинхронна обробка, Spring Boot ефективно підтримує складні робочі процеси.
- Документація Spring — Події
- Документація Spring — ApplicationListener
- Документація Spring — @EventListener
- Документація Spring — Підтримка асинхронності
- Код Spring — SimpleApplicationEventMulticaster
Дякую за прочитання! Якщо вам сподобалась ця стаття, будь ласка, подумайте про те, щоб підкреслити, аплодувати, відповісти чи зв'язатися зі мною на Twitter/X це дуже цінується та допомагає утримувати таку інформацію безкоштовною!
Іконка Spring Boot від Icons8
Перекладено з: How the Mechanics of Application Context Events Work in Spring Boot