После Цинмина я начал молча с нетерпением ждать 51-го праздника.
Обратный отсчет до 51: осталось 25 дней! ! ! !
Привет? Поучись немного и отдохни. Уху, взлетай
В этой статье приведен полный пример кода, см.GitHub.com/Yu — День V/SP…изlab-54содержание.
Оригинальность не так проста, дай мне немногоStarЭй, пошли утку!
1 Обзор
В режиме конструктораШаблон наблюдателяЭто относительно распространенный шаблон проектирования. Википедия объясняет это следующим образом:
FROM en.wikipedia.org/wiki/Наблюдатель-Шаблон
Паттерн Наблюдатель — это один из паттернов проектирования программного обеспечения. В этом шаблоне целевой объект управляет всеми зависимыми от него объектами-наблюдателями и активно уведомляется об изменении своего состояния. Обычно это достигается путем вызова методов, предоставляемых каждым наблюдателем.
Этот шаблон часто используется в системах обработки событий в реальном времени.
В нашем повседневном развитии бизнеса шаблон наблюдателя играет для нас очень важную роль, которая заключается в реализации бизнеса.разъединение. В качестве примера возьмем сценарий регистрации пользователя Предположим, что когда регистрация пользователя завершена, необходимо отправить пользователю электронное письмо, отправить купон на скидку и т. д., как показано на следующем рисунке:
- После того как UserService завершит свою собственную логику регистрации пользователей, ему нужно только опубликовать событие UserRegisterEvent, не обращая внимания на другую логику расширения.
- Другие службы могутСвояПодпишитесь на событие UserRegisterEvent, чтобы реализовать пользовательскую логику расширения.
Дружеское напоминание: много раз мы будемШаблон наблюдателяимодель публикации-подпискиСравните их вместе.
Проще говоря, модель публикации-подписки принадлежитв целомРежим наблюдателя режима наблюдателя, основанный на Субъекте и Наблюдателе режима наблюдателя, вводит Канал События.посредник, дальнейшая развязка. Как показано ниже:
Для дальнейшего обсуждения толстые друзья могут взглянутьВ чем разница между шаблоном наблюдателя и шаблоном публикации-подписки? 》обсуждение.
2. Механизм весеннего события
На основе шаблона наблюдателя Spring реализует собственный механизм событий, состоящий из трех частей:
- событиеApplicationEvent:пройти черезнаследоватьОн реализует пользовательские события. Кроме того, через свою
source
Свойства могут получать событияисточник,timestamp
свойство, чтобы получить время возникновения. - событиедиктор ApplicationEventPublisher: Через него можно публиковать события.
- событиеслушатель ApplicationListener:пройти черезвыполнитьОн прослушивает события указанного типа.
Дружеское напоминание: JDK также имеет встроенную реализацию механизма событий. Учитывая универсальность, механизм событий Spring основан на его расширении. Следовательно, ApplicationEvent наследуется от
java.util.EventObject
, ApplicationListener наследуется отjava.util.EventListener
.
3. Пример начала работы
Пример кода соответствует складу:
lab-54-demo
.
После прочтения некоторых основных концепций давайте рассмотрим вводный пример механизма событий Spring. Конкретная сцена по-прежнему основана наРегистрация пользователяНапример. новыйlab-54-demo
Проект, окончательный проект выглядит следующим образом:
3.1 Знакомство с зависимостями
существуетpom.xml
В файл вводятся соответствующие зависимости.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-54-demo</artifactId>
<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
вводитьspring-boot-starter-web
Причина зависимости в том, что пример интерфейса API будет предоставлен позже для простоты тестирования.
3.2 UserRegisterEvent
СоздайтеUserRegisterEventКласс Event наследует класс ApplicationEvent, и пользователь регистрирует событие. код показывает, как показано ниже:
public class UserRegisterEvent extends ApplicationEvent {
/**
* 用户名
*/
private String username;
public UserRegisterEvent(Object source) {
super(source);
}
public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
Путем определения переменных-членов в классе UserRegisterEventusername
, с прикрепленным именем пользователя.
3.3 UserService
СоздайтеUserServiceКласс, обслуживание пользователей. код показывает, как показано ниже:
@Service
public class UserService implements ApplicationEventPublisherAware { // <1>
private Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String username) {
// ... 执行注册逻辑
logger.info("[register][执行用户({}) 的注册逻辑]", username);
// <2> ... 发布
applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
}
}
<1>
место, реализоватьApplicationEventPublisherAwareинтерфейс, внедрив в него ApplicationEventPublisher.
<2>
На этом этапе, после выполнения логики регистрации, вызовите ApplicationEventPublisher's#publishEvent(ApplicationEvent event)
метод, опубликовать«3.2 Событие Регистрации пользователя»событие.
3.4 EmailService
СоздайтеEmailServiceКласс, Служба почтовых ящиков. код показывает, как показано ниже:
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> { // <1>
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
@Async // <3>
public void onApplicationEvent(UserRegisterEvent event) { // <2>
logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
}
}
<1>
На месте реализовать интерфейс ApplicationListener, черезE
Общий устанавливает интересующее событие.
<2>
место, реализовать#onApplicationEvent(E event)
метод для выполнения пользовательской обработки отслеживаемого события UserRegisterEvent.
【Не могу добавить】<3>
место, вишенка на торте, набор@Async
Аннотация, объявляющая асинхронное выполнение. В конце концов, в реальных сценариях отправка писем может быть медленной и некритичной по логике.
Дружеское напоминание: да
@Async
Аннотируйте заинтересованных толстых друзей, вы можете прочитать«Введение в асинхронные задачи Spring Boot»статья.
3.5 CouponService
СоздайтеCouponServiceКласс, купон Сервис. код показывает, как показано ниже:
@Service
public class CouponService {
private Logger logger = LoggerFactory.getLogger(getClass());
@EventListener // <1>
public void addCoupon(UserRegisterEvent event) {
logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
}
}
<1>
где в методе добавить@EventListener
Аннотируйте и установите для события прослушивания значение UserRegisterEvent. Вот еще один способ его использования!
3.6 DemoController
СоздайтеDemoControllerкласс, обеспечивающий/demo/register
Зарегистрируйте интерфейс. код показывает, как показано ниже:
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private UserService userService;
@GetMapping("/register")
public String register(String username) {
userService.register(username);
return "success";
}
}
3.7 DemoApplication
СоздайтеDemoApplicationкласс, класс запуска приложения. код показывает, как показано ниже:
@SpringBootApplication
@EnableAsync // 开启 Spring 异步的功能
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.8 Простой тест
① Выполните класс DemoApplication, чтобы запустить проект.
② звонокhttp://127.0.0.1:8080/demo/register?username=yudaoyuanmaинтерфейс для регистрации. Журнал печати консоли IDEA выглядит следующим образом:
# UserService 发布 UserRegisterEvent 事件
2020-04-06 13:09:39.145 INFO 18615 --- [nio-8080-exec-1] c.i.s.l.eventdemo.service.UserService : [register][执行用户(yudaoyuanma) 的注册逻辑]
# CouponService 监听处理该事件
2020-04-06 13:09:39.147 INFO 18615 --- [nio-8080-exec-1] c.i.s.l.eventdemo.service.CouponService : [addCoupon][给用户(yudaoyuanma) 发放优惠劵]
# EmailService 监听处理该事件
2020-04-06 13:09:39.154 INFO 18615 --- [ task-1] c.i.s.l.eventdemo.service.EmailService : [onApplicationEvent][给用户(yudaoyuanma) 发送邮件]
4. Встроенные события Spring
В среде Spring многие настраиваемые события настраиваются, что облегчает нам расширение. Ниже мы кратко приведем несколько примеров.
4.1 ApplicationContextEvent
ApplicationContextEventвеснаContextСоответствующие базовые классы событий, как показано на следующем рисунке:
Дружеское напоминание: Spring Context можно просто понимать как контейнер IoC.
- ContextStartedEvent: Spring Context запущенЗаканчиватьсобытие.
- ContextStoppedEvent: контекст Spring остановленЗаканчиватьсобытие.
- ContextClosedEvent: контекст Spring остановленНачинатьсобытие.
- ContextRefreshedEvent: инициализация или обновление Spring ContextЗаканчиватьсобытие.
То есть в течение всего жизненного цикла Spring Context будет опубликовано соответствующее событие ApplicationContextEvent.
SpringApplicationEventэто весенняя загрузкаApplicationБазовый класс событий, связанный с (приложением), как показано на следующем рисунке:
- ApplicationStartingEvent: приложение запускаетсяНачинатьсобытие.
- ApplicationEnvironmentPreparedEvent: ВеснаEnvironmentГотов завершить мероприятие.
- ApplicationContextInitializedEvent: контекст Spring готов к завершению, ноBean Definitionсобытие не загружено
- ApplicationPreparedEvent: контекст Spring готов к завершению, но не обновлен.
- ApplicationReadyEvent: приложение запущеноуспехсобытие.
- ApplicationFailedEvent: приложение запущеноПотерпеть поражениесобытие.
То есть в течение всего жизненного цикла Приложения будет опубликовано соответствующее событие SpringApplicationEvent.
Через события ApplicationContextEvent и SpringApplicationEvent мы находимся в«Введение в Spring Boot Continuous Delivery Jenkins»статьи«3. Элегантность на линии и вне ее»В этом разделе реализован элегантный онлайн и офлайн Spring Boot + Nginx.
4.2 RouteRefreshListener
существует«Введение в Spring Cloud Gateway Spring Cloud Gateway»статьи"6. Динамическая маршрутизация на основе центра конфигурации Nacos"В этом разделе мы видим, что Spring Cloud Gateway прослушиваетRefreshRoutesEventСобытия, объединенные с Nacos в качестве центра конфигурации, реализуютДинамическое обновление маршрутизации шлюзафункция.
дружеское напоминание:Spring Cloud Zuulтакже путем мониторингаRoutesRefreshedEventсобытие, реализацияДинамическое обновление маршрутизации шлюзафункция.
4.3 RefreshRemoteApplicationEvent
существует«Введение в Spring Cloud Config, Центр конфигурации Spring Cloud»статьи«5. Автоматическое обновление конфигурации (второй маркер)»В этом разделе мы видим, что клиент Spring Cloud Config прослушиваетRefreshRemoteApplicationEventСобытия в сочетании с RabbitMQ в качестве шины сообщений Spring Cloud Bus реализуютобновление локальной конфигурациифункция.
666. Пасхальное яйцо
На данный момент мы завершили изучение механизма событий Spring. Конечно, есть еще некоторые функции, толстые друзья могут подниматься и опускаться сами по себе.
① Если толстые друзья хотят нескольких слушателей, как указаноприказреализации, которая может быть достигнута путемOrderedинтерфейсы, указав их порядок.
② Если вы хотите прослушивать различные события ApplicationContext, вы можете реализоватьSmartApplicationListenerИнтерфейс можно посмотреть на конкретном примереSourceFilteringListenerсвоего рода.
③@TransactionalEventListener
Аннотация, вы можете объявить, что соответствующая логика мониторинга выполняется, когда текущая транзакция «заканчивается».
④ Это может быть достигнуто путемApplicationEventMulticasterИнтерфейс определяет настраиваемого распространителя событий, может добавлять и удалять слушателей, а также публиковать события для зарегистрированных в нем слушателей. Он редко используется и в основном может быть проигнорирован.