Реализуйте разделение бизнес-кода: модель Spring, управляемая событиями, действительно элегантна в использовании!
Реализуйте разделение бизнес-кода: модель Spring, управляемая событиями, действительно элегантна в использовании!

Путь к росту программиста

Интернет/Программист/Технологии/Обмен данными

Чтение этой статьи займет около 6 минут.

Откуда: juejin.cn/post/7301910992320479284

Приведите пример

Большинство программного обеспечения или приложений имеют систему членства. Когда мы регистрируемся в качестве участника, продавец обычно привлекает нас в группу участников, отправляет нам купоны, отправляет приветственные сообщения и т. д.

Стоит отметить:

  1. Следующие действия произойдут только после успешной регистрации;
  2. Порядок выполнения этих действий после успешной регистрации отсутствует;
  3. Результаты выполнения этих действий после успешной регистрации не могут влиять друг на друга;

традиционное письмо

Язык кода:javascript
копировать
public Boolean doRegisterVip(){
 //1. Зарегистрируйтесь как участник.
 registerVip();
 //2. Присоединяйтесь к группе участников.
 joinMembershipGroup();
 //3. Отправка купонов
 issueCoupons();
 //4. Push-сообщение
 sendWelcomeMsg();
}

Такой способ записи связывает все действия в методе doRegisterVip. Во-первых, эффективность выполнения низкая, во-вторых, степень связи слишком высока, и, наконец, его нелегко расширить. Так как же оптимизировать эту логику?

Введение в принцип событийно-ориентированной модели

Модель Spring, управляемая событиями, состоит из трех частей:

событие:Настраиваемый пользователемсобытиепредставлены классами и связанными атрибутами и поведениямисобытиеособенность,После Spring 4.2 нет необходимости явно наследовать класс ApplicationEvent при определении события.,Просто определите компонент напрямую,Spring автоматически перенесет событие через PayloadApplicationEvent.

Опубликовано событием:существоватьSpringСносноApplicationEventPublisherПучоксобытиепубликовать,Таким образом, контент может потребляться и обрабатываться слушателем.

событиеслушатель:ApplicationListener,Мониторинг релизов событий,Обработка последующих операций после возникновения события.

Схема следующая:

Реализация кода

Определение основных элементов

Издатель события: EventEngine.java, EventEngineImpl.java.

Язык кода:javascript
копировать
package com.example.event.config;

/**
 * событиедвигатель */
public interface EventEngine {

    /**
     * отправлятьсобытие     *
     * @param event событие
     */
    void publishEvent(BizEvent event);
}
Язык кода:javascript
копировать
package com.example.event.config;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

import org.springframework.util.CollectionUtils;

/**
 * класс реализации обработчика событий
 */
public class EventEngineImpl implements EventEngine {

    /**
     * Асинхронный исполнитель. Системе также необходимо определить собственный пул потоков.
     */
    private Executor bizListenerExecutor;

    /**
     * Будет ли асинхронным, значение по умолчанию — false
     */
    private boolean async;

    /**
     * подписчик KEY — ТЕМА, VALUES — коллекция слушателей.
     */
    private Map<String, List<BizEventListener>> bizSubscribers = new HashMap<>(16);

    @Override
    public void publishEvent(BizEvent event) {
        List<BizEventListener> listeners = bizSubscribers.get(event.getTopic());
        if (CollectionUtils.isEmpty(listeners)) {
            return;
        }
        for (BizEventListener bizEventListener : listeners) {
            if (bizEventListener.decide(event)) {
                //Если выполняется асинхронно, поместите его в пул потоков
                if (async) {
                    bizListenerExecutor.execute(new EventSubscriber(bizEventListener, event));
                } else {
                    bizEventListener.onEvent(event);
                }

            }
        }
    }

    /**
     * Setter method for property <tt>bizListenerExecutor</tt>.
     *
     * @param bizListenerExecutor value to be assigned to property bizListenerExecutor
     */
    public void setBizListenerExecutor(Executor bizListenerExecutor) {
        this.bizListenerExecutor = bizListenerExecutor;
    }

    /**
     * Setter method for property <tt>bizSubscribers</tt>.
     *
     * @param bizSubscribers value to be assigned to property bizSubscribers
     */
    public void setBizSubscribers(Map<String, List<BizEventListener>> bizSubscribers) {
        this.bizSubscribers = bizSubscribers;
    }

    /**
     * Setter method for property <tt>async</tt>.
     *
     * @param async value to be assigned to property async
     */
    public void setAsync(boolean async) {
        this.async = async;
    }
}

Событие: BizEvent.java

Язык кода:javascript
копировать
package com.example.event.config;

import java.util.EventObject;

/**
 * бизнессобытие */
public class BizEvent extends EventObject {

    /**
     * Topic
     */
    private final String topic;

    /**
     * идентификатор компании
     */
    private final String bizId;

    /**
     * данные
     */
    private final Object data;

    /**
     * @param topic eventtopic, используется для различения типов событий
     * @param bizId Идентификатор компании, идентифицирующий этот звонок
     * @param data  объект EventTransfer
     */
    public BizEvent(String topic, String bizId, Object data) {
        super(data);
        this.topic = topic;
        this.bizId = bizId;
        this.data = data;
    }

    /**
     * Getter method for property <tt>topic</tt>.
     *
     * @return property value of topic
     */
    public String getTopic() {
        return topic;
    }

    /**
     * Getter method for property <tt>id</tt>.
     *
     * @return property value of id
     */
    public String getBizId() {
        return bizId;
    }

    /**
     * Getter method for property <tt>data</tt>.
     *
     * @return property value of data
     */
    public Object getData() {
        return data;
    }
}

Прослушиватель событий: EventSubscriber.java

Язык кода:javascript
копировать
package com.example.event.config;

/**
 * Слушатель событий. Примечание. В настоящее время контекст потока отсутствует. При необходимости измените конструктор, чтобы отобразить копированную информацию о контексте.
 */
public class EventSubscriber implements Runnable {

    /**
     * Бизнес-слушатель
     **/
    private BizEventListener bizEventListener;

    /**
     * бизнессобытие     */
    private BizEvent bizEvent;

    /**
     * @param bizEventListener событиеслушатель     * @param bizEvent         событие
     */
    public EventSubscriber(BizEventListener bizEventListener, BizEvent bizEvent) {
        super();
        this.bizEventListener = bizEventListener;
        this.bizEvent = bizEvent;
    }

    @Override
    public void run() {
        bizEventListener.onEvent(bizEvent);
    }
}

Другие компоненты

Прослушиватель бизнес-событий: BizEventListener.java

Язык кода:javascript
копировать
package com.example.event.config;

import java.util.EventListener;

/**
 * Слушатель деловых мероприятий
 *
 */
public interface BizEventListener extends EventListener {

    /**
     * Выполнять ли событие
     *
     * @param event событие
     * @return
     */
    public boolean decide(BizEvent event);

    /**
     * осуществлятьсобытие     *
     * @param event событие
     */
    public void onEvent(BizEvent event);
}

Тема обработчика событий: EventEngineTopic.java

Язык кода:javascript
копировать
package com.example.event.config;

/**
 * тема механизма событий, используемая для различения типов событий
 */
public class EventEngineTopic {
    /**
     * Присоединяйтесь к группе участников
     */
    public static final String JOIN_MEMBERSHIP_GROUP = "joinMembershipGroup";

    /**
     * Отправить купоны
     */
    public static final String ISSUE_COUPONS = "issueCoupons";

    /**
     * push-сообщение
     */
    public static final String SEND_WELCOME_MSG = "sendWelcomeMsg";

}

Реализация прослушивателя

Обработчик купонов: CouponsHandlerListener.java

Язык кода:javascript
копировать
package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * Купонный процессор
 */
@Component
public class CouponsHandlerListener implements BizEventListener {

    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("Купонный процессор:выдан купон на скидку 10%");
    }
}

Обработчик группы членства: MembershipHandlerListener.java

Язык кода:javascript
копировать
package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * Процессор группы участников
 */
@Component
public class MembershipHandlerListener implements BizEventListener {
    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("Процессор группы участников:Вы успешно добавили Присоединяйтесь к группе участников");
    }
}

Обработчик push-сообщений: MsgHandlerListener.java

Язык кода:javascript
копировать
package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * Процессор push-сообщений
 */
@Component
public class MsgHandlerListener implements BizEventListener {

    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("Процессор push-сообщений:Добро пожаловать, чтобы стать участником! ! ! ");
    }
}

Конфигурация механизма, управляемого событиями: EventEngineConfig.java

Язык кода:javascript
копировать
package com.example.event.listener;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.example.event.config.BizEventListener;
import com.example.event.config.EventEngine;
import com.example.event.config.EventEngineImpl;
import com.example.event.config.EventEngineTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

/**
 * Конфигурация движка eventDrive
 */
@Configuration
public class EventEngineConfig {
    /**
     * Асинхронная обработка пула потоковсобытие
     */
    private static final Executor EXECUTOR = new ThreadPoolExecutor(20, 50, 10, TimeUnit.MINUTES,
        new LinkedBlockingQueue(500), new CustomizableThreadFactory("EVENT_ENGINE_POOL"));

    @Bean("eventEngineJob")
    public EventEngine initJobEngine(CouponsHandlerListener couponsHandlerListener,
        MembershipHandlerListener membershipHandlerListener,
        MsgHandlerListener msgHandlerListener) {
        Map<String, List<BizEventListener>> bizEvenListenerMap = new HashMap<>();
        //Регистрация купона
        bizEvenListenerMap.put(EventEngineTopic.ISSUE_COUPONS, Arrays.asList(couponsHandlerListener));
        //Регистрируем событие группы участников
        bizEvenListenerMap.put(EventEngineTopic.JOIN_MEMBERSHIP_GROUP, Arrays.asList(membershipHandlerListener));
        //Регистрируемся для события push-сообщения
        bizEvenListenerMap.put(EventEngineTopic.SEND_WELCOME_MSG, Arrays.asList(msgHandlerListener));

        EventEngineImpl eventEngine = new EventEngineImpl();
        eventEngine.setBizSubscribers(bizEvenListenerMap);
        eventEngine.setAsync(true);
        eventEngine.setBizListenerExecutor(EXECUTOR);
        return eventEngine;
    }
}

Тестовый класс

TestController.java

Язык кода:javascript
копировать
package com.example.event.controller;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.annotation.Resource;

import com.example.event.config.BizEvent;
import com.example.event.config.EventEngine;
import com.example.event.config.EventEngineTopic;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * тест
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Resource(name = "eventEngineJob")
    private EventEngine eventEngine;

    @GetMapping("/doRegisterVip")
    public String doRegisterVip(@RequestParam(required = true) String userName,
        @RequestParam(required = true) Integer age) {
        Map<String, Object> paramMap = new HashMap<>(16);
        paramMap.put("userName", userName);
        paramMap.put("age", age);
        //1. Зарегистрируйтесь как участник.,Здесь не реализовано
        System.out.println("Успешно зарегистрирован участник");
        //2. Присоединяйтесь к группе участников.
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.JOIN_MEMBERSHIP_GROUP, UUID.randomUUID().toString(), paramMap));
        //3. Отправка купонов
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.ISSUE_COUPONS, UUID.randomUUID().toString(), paramMap));
        //4. Push-сообщение
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.SEND_WELCOME_MSG, UUID.randomUUID().toString(), paramMap));
        return «Зарегистрирован успешно»;
    }
}

Структура кода проекта

Интерфейс вызова

http://localhost:8080/test/doRegisterVip?userName=zhangsan&age=28

<END>

Язык кода:javascript
копировать
Содержимое включает основы Java.、JavaWeb、Оптимизация производительности MySQL、JVM、Замок、Миллионы одновременных、очередь сообщений、Высокопроизводительное кэширование、отражение、Принцип пружинного семейного ведра、микросервисы、Zookeeper... и другие технологические стеки!
boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose