В предыдущей статье мы объяснилиОсновные понятия шаблона публикации-подписки, и пройтиКоманда Pub/Sub для RedisДемонстрирует его реализацию в распределенном сценарии. по сравнению с тем, что будет обсуждаться позжеGuava EventBus, можно сказать, что использование Spring Events более распространено, а его функции более мощные.
События — это часто упускаемая из виду важная функция платформы и распространенная реализация шаблона публикации/подписки. Сам Spring Framework управляется событиями.
Давайте взглянем на модель, управляемую событиями, в контейнере Spring, а затем быстро реализуем публикацию и мониторинг настраиваемых событий вместе, а затем обсудим, как реализовать синхронный и асинхронный мониторинг событий, а затем представим, как определить прослушиватель. и, наконец, предоставить условный механизм мониторинга событий на основе SpEL (Spring Expression Language).
После прохождения этого курса вы найдете:
- Разделение кода с помощью механизма событий сделает ваш код очень чистым и легко расширяемым.
- Механизм асинхронной обработки событий может значительно повысить скорость отклика программы, а внутренний пул потоков значительно повысит эффективность параллелизма программы.
- Условные и универсальные прослушиватели позволяют сократить объем явной логики, делая каждый прослушиватель событий более атомарным.
🚜Адрес Github исходного кода этой статьи
Собственная модель Spring, управляемая событиями
Контейнер Spring и модель событий
Механизм событий Spring в основном предоставляет следующие интерфейсы и классы:
- ApplicationContextEvent
Абстрактный класс событий, предоставляемый Spring, вы можете наследовать его для реализации пользовательских событий.
- ApplicationEventMulticaster
ApplicationEventMulticasterявляется вещателем событий, его роль заключается в том, чтобы поставитьApplicationcontextОпубликованоEventТрансляция для всех слушателей.
- ApplicationListener
ApplicationListenerунаследовано отEventListener, все слушатели должны реализовать этот интерфейс.
Этот интерфейс имеет только один метод onApplicationEvent(), который принимаетApplicationEventИли его объект подкласса в качестве параметра, в теле метода соответствующая обработка может осуществляться разными суждениями класса Event.
Когда событие срабатывает, все слушатели получат сообщение.Если вам нужны требования к порядку приема слушателей, вы можете реализовать реализацию этого интерфейса.SmartApplicationListener, через этот интерфейс можно указать порядок, в котором слушатель получает события.
- ApplicationContext
Реализация механизма событий требует трех частей: источника события, события и прослушивателя событий.
описано вышеApplicationEventэквивалент события,ApplicationListenerЭквивалент прослушивателя событий, источник события здесь говорит, чтоApplicationContext.
ApplicationContextЭто глобальный контейнер в Spring, также называемый «контекстом приложения», который отвечает за чтение конфигурации бинов, управление загрузкой бинов и поддержание зависимостей между бинами, то есть отвечает за управление всем жизненным циклом бинов .
ApplicationContextЭто то, что мы обычно называем контейнером IOC.
- ApplicationContextAware
Когда класс реализуетApplicationContextAwareПосле интерфейса bean-компоненты интерфейса Aware могут получить некоторые соответствующие ресурсы после инициализации.Этот класс может напрямую получить все внедренные объекты bean-компонентов в файле конфигурации spring.
Весна дает много
AwareКонец конца, реализуя эти интерфейсы, вы получаете возможность получать ресурсы в контейнере Spring.
Сам Spring реализует следующие 4 события:
- ContextStartedEvent (контейнер запущен)
- ContextStoppedEvent (контейнер остановлен)
- ContextClosedEvent (контейнер закрыт)
- ContextRefreshedEvent (обновление контейнера)
Реализация пользовательских событий Spring
Для настройки модели событий Spring требуются три роли: Event, Publisher и Listener.
пользовательское событие
Зарегистрированное событие настраивается ниже.Конструктор этого события предоставляет два параметра, один — источник публикации (source), а другой — сообщение (message).
package net.ijiangtao.tech.designpattern.pubsub.spring.common;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* 注册事件
* @author ijiangtao
* @create 2019-05-02 12:59
**/
@Getter
public class RegisterEvent extends ApplicationEvent {
private String message;
public RegisterEvent(Object source, String message) {
super(source);
this.message = message;
}
}
Настроить издателя
Издатель, который публикует пользовательские события, указан ниже, мы передаемApplicationEventPublisherопубликовать событие.
package net.ijiangtao.tech.designpattern.pubsub.spring.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
/**
* 注册事件发布器
* @author ijiangtao
* @create 2019-05-02 13:01
**/
@Component
@Slf4j
public class RegisterEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publish(final String message) {
log.info("publis a RegisterEvent,message:{}", message + " time: " + LocalTime.now());
RegisterEvent registerEvent = new RegisterEvent(this, message);
applicationEventPublisher.publishEvent(registerEvent);
}
}
пользовательский слушатель
Ниже приведен прослушиватель нескольких пользовательских событий, все они достигаютApplicationListener<RegisterEvent>Интерфейс, и для того, чтобы имитировать процесс обработки событий, текущий поток спать в течение 3 секунд. Поскольку процесс реализации аналогичен, здесь предоставляется только одна реализация.
package net.ijiangtao.tech.designpattern.pubsub.spring.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
/**
* 发送注册成功邮件提醒
*
* @author ijiangtao
* @create 2019-05-02 13:07
**/
@Component
@Slf4j
public class SendRegisterEmailListener implements ApplicationListener<RegisterEvent> {
@Override
public void onApplicationEvent(RegisterEvent event) {
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
log.error("{}", e);
}
log.info("SendRegisterEmailListener message: " + event.getMessage()+" time: "+ LocalTime.now());
}
}
Тестировать пользовательский механизм событий
Пользовательское событие опубликовано ниже с модульным тестом. Наблюдая за выводом журнала, можно обнаружить, что после того, как событие выпущено, каждый слушатель по очереди выводит журнал мониторинга.
package net.ijiangtao.tech.designpattern.pubsub.spring;
import lombok.extern.slf4j.Slf4j;
import net.ijiangtao.tech.designpattern.pubsub.spring.common.RegisterEventPublisher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Spring Events
*
* @author ijiangtao
* @create 2019-05-02 12:53
**/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class SpringEventsCommonTests {
@Autowired
private RegisterEventPublisher registerEventPublisher;
@Test
public void test1(){
registerEventPublisher.publish(" Danny is here.");
try {
Thread.sleep(10 * 1000);
} catch (Exception e) {
log.error("{}", e);
}
}
}
Таким образом реализуется прослушиватель событий на основе Spring Events.
Реализация асинхронных событий Spring
Наблюдая за временем, напечатанным в журнале, вы обнаружите, что все зарегистрированные выше слушатели выполняются последовательно, то есть обработка событий Spring Events по умолчанию синхронна. Синхронный мониторинг событий занимает много времени, и необходимо дождаться окончания предыдущего процесса мониторинга, прежде чем можно будет выполнить следующий прослушиватель.
Так можно ли изменить его на асинхронное прослушивание? Ответ положительный. Две реализации описаны ниже.
настроить
предоставляется через JDKSimpleApplicationEventMulticasterРассылая событие, вы можете асинхронно и одновременно разрешить нескольким слушателям одновременно выполнять действия по прослушиванию событий.
package net.ijiangtao.tech.designpattern.pubsub.spring.async.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
/**
* 异步事件监听配置
*
* @author ijiangtao
* @create 2019-05-02 13:23
**/
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
пройти черезSimpleApplicationEventMulticasterИсходный код можно увидеть в егоmulticastEventМетод будет выполнять действие публикации события одновременно через пул потоков.
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Iterator var4 = this.getApplicationListeners(event, type).iterator();
while(var4.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var4.next();
Executor executor = this.getTaskExecutor();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
аннотация
Публикуйте события по аннотации, просто добавьте Listener@Async, а в месте размещения события добавить@EnableAsyncПросто аннотируйте.
package net.ijiangtao.tech.designpattern.pubsub.spring.async.annotation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
/**
* 发送优惠券
*
* @author ijiangtao
* @create 2019-05-02 13:07
**/
@Component
@Slf4j
public class UserActionListenerAsyncAnnotation implements ApplicationListener<RegisterEvent> {
@Async
@Override
public void onApplicationEvent(RegisterEvent event) {
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
log.error("{}", e);
}
log.info("UserActionListener message: " + event.getMessage()+" time: "+ LocalTime.now());
}
}
package net.ijiangtao.tech.designpattern.pubsub.spring;
import lombok.extern.slf4j.Slf4j;
import net.ijiangtao.tech.designpattern.pubsub.spring.async.annotation.RegisterEventPublisherAsyncAnnotation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Spring Events
*
* @author ijiangtao
* @create 2019-05-02 12:53
**/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@EnableAsync
public class SpringEventsAsyncAnnotationTests {
@Autowired
private RegisterEventPublisherAsyncAnnotation registerEventPublisherAsyncAnnotation;
@Test
public void test2() {
registerEventPublisherAsyncAnnotation.publish(" Danny is here (Async).");
try {
Thread.sleep(10 * 1000);
} catch (Exception e) {
log.error("{}", e);
}
}
}
Внедрение интеллектуальных слушателей
путем реализацииSmartApplicationListenerИнтерфейс, вы можете настроить порядок выполнения слушателей, поддерживаемые типы событий и т. д.
package net.ijiangtao.tech.designpattern.pubsub.spring.smart;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* event
*
* @author ijiangtao
* @create 2019-05-02 15:33
**/
@Getter
public class SmartEvent extends ApplicationEvent {
private String message;
public SmartEvent(Object source, String message) {
super(source);
}
}
package net.ijiangtao.tech.designpattern.pubsub.spring.smart;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
* SmartApplicationListener
*
* @author ijiangtao
* @create 2019-05-02 15:32
**/
@Component
@Slf4j
public class CustomSmartApplicationListener1 implements SmartApplicationListener {
/**
* 自定义支持的事件类型
* @param eventType
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return eventType == SmartEvent.class;
}
/**
* 定义支持的事件源类型
* @param sourceType
* @return
*/
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return sourceType == String.class;
}
/**
* 自定义优先级别
* @return
*/
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
log.info("CustomSmartApplicationListener {}",applicationEvent.getSource());
}
}
Условные прослушиватели событий
Иногда нам нужно, чтобы прослушиватель прослушивал несколько событий, например, прослушиватель безопасности системы (SecurityEventListener), который может прослушивать различные проблемы безопасности системы (NetWorkSecurityEvent, SQLSecurityEvent, AuthorizationSecurityEvent и т. д.). Это время, когда вы можете позволить прослушивателю прослушивать Родительский класс этих событийSecurityEvent, чтобы ваши слушатели могли прослушивать все подтипы этого события.
Иногда нам нужно решить, какой прослушиватель использовать на основе определенного значения сообщения, выданного одним и тем же событием. НапримерSecurityEventиметь уровень безопасностиlevelСвойства, вы определяете 5 уровней, каждый уровень имеет другой механизм обработки. В соответствии с традиционным методом реализации это должно быть реализовано условным суждением (if/else или switch/case и т. д.), а инкапсуляция кода не является хорошей. В этом случае вы можете добавить метод прослушивателя вашего Listener@EventListenerаннотации и черезconditionпараметры для указания условий фильтрации. Напримерcondition = "#event.success eq false")Это выражается SpEL: метод мониторинга выполняется только тогда, когда атрибут успеха переменной события параметра метода равен false.
Ниже мы демонстрируем процесс реализации.
Предоставляет общий базовый класс Event:
package net.ijiangtao.tech.designpattern.pubsub.spring.generic;
import lombok.Getter;
/**
* GenericSpringEvent
*
* @author ijiangtao
* @create 2019-05-02 13:47
**/
@Getter
public class GenericSpringEvent<T> {
private T what;
protected boolean success;
public GenericSpringEvent(T what, boolean success) {
this.what = what;
this.success = success;
}
}
Настройка реализации Event на основе базового класса Event
package net.ijiangtao.tech.designpattern.pubsub.spring.generic.checkout;
import lombok.Getter;
import net.ijiangtao.tech.designpattern.pubsub.spring.generic.GenericSpringEvent;
/**
* GenericSpringEventCheckout
*
* @author ijiangtao
* @create 2019-05-02 13:58
**/
@Getter
public class GenericSpringEventCheckout extends GenericSpringEvent<Long> {
private Long userId;
public GenericSpringEventCheckout(Long userId, boolean success) {
super(userId, success);
}
}
Предоставление условных прослушивателей
Слушатель слушает все подклассы базового класса Event и передает@EventListenerАннотации и SpEL определяют условия фильтрации событий прослушивания.
package net.ijiangtao.tech.designpattern.pubsub.spring.generic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @author ijiangtao
* @create 2019-05-02 13:52
**/
@Component
@Slf4j
public class GenericSpringEventSuccessListenerLong {
@EventListener(condition = "#event.success")
public void handle(GenericSpringEvent<Long> event) {
log.info("Handling generic event Success (conditional). {}",event.getWhat());
}
}
package net.ijiangtao.tech.designpattern.pubsub.spring.generic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @author ijiangtao
* @create 2019-05-02 13:52
**/
@Component
@Slf4j
public class GenericSpringEventFailListenerLong {
@EventListener(condition = "#event.success eq false")
public void handle(GenericSpringEvent<Long> event) {
log.info("Handling generic event Fail (conditional). {}",event.getWhat());
}
}
издатель пользовательских событий
package net.ijiangtao.tech.designpattern.pubsub.spring.generic.checkout;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
/**
* GenericSpringEventPublisher
*
* @author ijiangtao
* @create 2019-05-02 13:55
**/
@Component
@Slf4j
public class GenericSpringEventPublisherCheckout {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publish(final Long userId, boolean success) {
log.info("publis a GenericSpringEventPublisher, userId:{}", userId + " time: " + LocalTime.now());
GenericSpringEventCheckout eventCheckout = new GenericSpringEventCheckout(userId, success);
applicationEventPublisher.publishEvent(eventCheckout);
}
}
модульный тест
Метод тестирования представлен ниже.Наблюдая за журналом, было обнаружено, что разные условия запускают разные слушатели.
package net.ijiangtao.tech.designpattern.pubsub.spring;
import lombok.extern.slf4j.Slf4j;
import net.ijiangtao.tech.designpattern.pubsub.spring.generic.checkout.GenericSpringEventPublisherCheckout;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEvent;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Spring Events
*
* @author ijiangtao
* @create 2019-05-02 12:53
**/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class SpringEventsGenericTests {
@Autowired
private GenericSpringEventPublisherCheckout checkoutPubliser;
@Test
public void test1() {
ApplicationEvent applicationEvent;
checkoutPubliser.publish(101L, true);
checkoutPubliser.publish(202L, false);
}
}