Прекратите программировать циклы, собственный шаблон наблюдателя Spring восхитителен!

Java задняя часть
Прекратите программировать циклы, собственный шаблон наблюдателя Spring восхитителен!

После Цинмина я начал молча с нетерпением ждать 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.

ApplicationContextEvent 类图

  • ContextStartedEvent: Spring Context запущенЗаканчиватьсобытие.
  • ContextStoppedEvent: контекст Spring остановленЗаканчиватьсобытие.
  • ContextClosedEvent: контекст Spring остановленНачинатьсобытие.
  • ContextRefreshedEvent: инициализация или обновление Spring ContextЗаканчиватьсобытие.

То есть в течение всего жизненного цикла Spring Context будет опубликовано соответствующее событие ApplicationContextEvent.

SpringApplicationEventэто весенняя загрузкаApplicationБазовый класс событий, связанный с (приложением), как показано на следующем рисунке:

SpringApplicationEvent 类图

  • 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 реализуютобновление локальной конфигурациифункция.

Spring Cloud Config

666. Пасхальное яйцо

На данный момент мы завершили изучение механизма событий Spring. Конечно, есть еще некоторые функции, толстые друзья могут подниматься и опускаться сами по себе.

① Если толстые друзья хотят нескольких слушателей, как указаноприказреализации, которая может быть достигнута путемOrderedинтерфейсы, указав их порядок.

② Если вы хотите прослушивать различные события ApplicationContext, вы можете реализоватьSmartApplicationListenerИнтерфейс можно посмотреть на конкретном примереSourceFilteringListenerсвоего рода.

@TransactionalEventListenerАннотация, вы можете объявить, что соответствующая логика мониторинга выполняется, когда текущая транзакция «заканчивается».

④ Это может быть достигнуто путемApplicationEventMulticasterИнтерфейс определяет настраиваемого распространителя событий, может добавлять и удалять слушателей, а также публиковать события для зарегистрированных в нем слушателей. Он редко используется и в основном может быть проигнорирован.