事件驱动

生活中的事件驱动:

  • 银行存款事件,触发更新余额、用户星级提升、贷款广告推送

  • 航班延误事件,触发广播通知客户、后续形成更新、酒店订单更新

事件驱动架构

事件驱动架构(Event-driven architecture,简称EDA),是指软件系统中发生了某些事件,导致软件的其他部分的操作或者执行。是一种软件设计的方法。

其中,事件是软件系统中某种显著的变化。

主要应用场景:

  • GUI,点击按钮发生点击事件,触发操作

  • 调试器,当程序运行系统当运行到了某个点时,发送事件,触发断点调试。这种关系称为反转控制关系,即主观上时调试器控制运行系统调试,客观上是运行系统通知调试器,推送了断点调试。

整个流程:

  1. 事件生产方(Event Producer)发生事件(Event happends),并通过事件发射器(Event Emitter)向事件通道(Event Channel)发送这个事件

  2. 事件管理器(Event Manager)接收到通道中的事件,对事件进行管理或者进行事件传播(Event disseminates)

  3. 事件将会由事件接收者(Event Sinks)接收,并交给他所属的事件消费者(Event Consumer)进行事件评估并采取相关的动作(Event's evaluated and action are token)

类比过程,以mq为例:

  1. 按钮发生点击事件,通过mq客户端向mq发送消息

  2. mq服务端接收到消息,根据topic向消费者发送消息

  3. 消费者的mq接收端接收到消息,将会使用程序消费消息,并作出响应的操作

观察者模式

被观察者(Subject)持有观察者(Observer)的引用,当被观察者发生变化时,将会自动调用观察观察者对象的通知方法。

观察者模式中,Subject既充当事件生产方,又充当事件管理器。

AWT

awt就是观察者模式的典型运用场景:

public class FooPanel extends JPanel implements ActionListener {
    public FooPanel() {
        super();
        JButton btn = new JButton("Click Me!");
        btn.addActionListener(this);
        this.add(btn);
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        System.out.println("Button has been clicked");
    }
}

上述代码中,组件类型JPanelSubjectActionListenerObserver,类型FooPanel即使观察者又是被观察者。

被观察者通过btn.addActionListener(this);代码将观察者的引用加入到内部中。

JDK Observer

Spring Event

Spring Event位于Spring的核心模块中,他主要通过核心类ApplicationEvent以及ApplicationListener接口提供功能。实现ApplicationListener并将其放入到IoC容器中,当有ApplicationEvent事件publish到ApplicationContext中时,就可以监听指定的事件并做出相应的操作。Spring Event基于典型的观察者模式实现。

集成ApplicationEvent可以实现自己的事件,在Spring内部提供了一套标准的内置事件:

  1. ContextRefreshedEvent

  2. ContextStartedEvent

  3. ContextStoppedEvent

  4. ContextClosedEvent

  5. RequestHandledEvent

  6. ServletRequestHandledEvent

示例代码:

/**
 * 自定义事件对象
 *
 * @author 杨顺翔
 * @since 2022/08/27
 */
public class MyEvent extends ApplicationEvent {

    private static final long serialVersionUID = 3223648309069468451L;

    public MyEvent(String message) {
        super(message);
    }

}
/**
 * 实现ApplicationListener接口,定义事件监听器,注入到容器中,监控某类型事件
 *
 * @author 杨顺翔
 * @since 2022/08/27
 */
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent contextStartedEvent) {
        System.out.println("MyEventListener监听到事件: " + contextStartedEvent.toString());
    }
}
/**
 * 事件发布类,也就是event provider,需要注入容器中ApplicationEventPublisher对象,并调用其 publish 方法发布一个事件
 * 另外,页提供了一个Aware接口可以获取到事件发布对象
 *
 * @author 杨顺翔
 * @since 2022/08/27
 */
@Component
public class MyEventPublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;
    
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
    
    public void sayHello() {
        System.out.println("hello");
        MyEvent e = new MyEvent("hello");
        applicationEventPublisher.publishEvent(e);
    }
}

/**
 * 此外,Spring内部提供了几个标准的事件
 * (较高版本的Spring在定义Listener时,不用集成ApplicationListener了,可以直接给方法增加 @EventListener注解标记该方法是一个时间处理方法)
 *
 * @author 杨顺翔
 * @since 2022/08/27
 */
@Component
public class StandardEventListener {

    /**
     * spring context 已启动事件处理
     */
    @EventListener(ContextStartedEvent.class)
    public void contextStated() {
        System.out.println("context started");
    }

    /**
     * 所有Bean已经刷新事件
     */
    @EventListener
    public void contextRefreshed(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println("context refreshed " + contextRefreshedEvent.getSource());
    }

    /**
     * 容器已经停止运行
     */
    @EventListener(ContextStoppedEvent.class)
    public void contextStopped() {
        System.out.println("context stopped");
    }

    /**
     * 容器已经完全关闭
     */
    @EventListener(ContextClosedEvent.class)
    public void contextClosed() {
        System.out.println("context closed");
    }
}
/**
 * Spring Event 事件处理,观察者模式
 */
@ComponentScan
@Configuration
public class SpringEventTest {

    @Test
    public void testContextStartedEvent() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringEventTest.class);
        context.start();
        // 业务的某个方法触发了某个事件
        // 如果没有加@Async注解,时间处理就是同步的,他与观察者模式别无二致
        MyEventPublisher myEventPublisher = context.getBean(MyEventPublisher.class);
        myEventPublisher.sayHello();
        // 停止容器
        context.stop();
        // 重新打开容器
        context.start();
        // 关闭容器
        context.close();
    }
    
}

发布订阅模式

将事件处理器单独的抽取出来,而不是像观察者模式一样合在一起,就是发布订阅模式。由事件管理器管理生产者与消费者,并控制事件的传输。

  1. 向事件管理器注册消息发布者与消费者

  2. 事件管理器将事件分为多个Topic

Google Guava Event Bus

  1. Publisher为事件生产方

  2. Event Bus为事件管理器

  3. Subscriber为事件消费方

使用Event Bus:

  1. 定义事件对象

    public class CustomEvent {
    
        private String action;
    
        public CustomEvent(String action) {
            this.action = action;
        }
    
        public String getAction() {
            return action;
        }
    
        public void setAction(String action) {
            this.action = action;
        }
    }
  2. 定义事件监听器

    public class MyEventListener {
    
        private static int eventHandled;
    
        private static final Logger LOGGER = LoggerFactory.getLogger(MyEventListener.class);
    
        /**
        * 事件监听器(事件消费者),订阅string类型的事件
        */
        @Subscribe
        public void stringEvent(String event) {
            LOGGER.info("发生string事件:" + event);
            eventHandled++;
        }
    
        /**
        * 监听CustomEvent类型的事件
        */
        @Subscribe
        public void customEvent(CustomEvent customEvent) {
            LOGGER.info("发生CustomEvent事件:" + customEvent);
            eventHandled++;
        }
    
        /**
        * 使用DeadEvent监听没有事件消费者的事件
        */
        @Subscribe
        public void deadEvent(DeadEvent deadEvent) {
            LOGGER.info("DeadEvent: " + deadEvent.getEvent());
        }
    
    }
  3. 使用事件监听器

    public class EventBusTest {
    
        private MyEventListener myEventListener;
    
        private EventBus eventBus;
    
        /**
        * 注册事件监听器
        */
        @BeforeAll
        public void setup() {
            eventBus = new EventBus();
            myEventListener = new MyEventListener();
    
            // 注册事件监听器
            eventBus.register(myEventListener);
        }
    
        /**
        * 结束后自动解除注册事件监听器
        */
        @AfterAll
        public void tearDown() {
            eventBus.unregister(myEventListener);
        }
    
    }

Spring AMQP

如何在观察者和发布订阅中选择

观察者模式和发布-订阅模式都是用于解耦发布者和订阅者之间的通信,但它们在一些方面有所不同。下面是它们的基本概念和适用场景:

观察者模式:

观察者模式基于一个主题(通常称为主题或可观察对象),它维护一组观察者对象并在状态发生变化时通知它们。主题和观察者之间是一对多的关系,一个主题可以有多个观察者。当主题的状态发生变化时,所有观察者都会收到通知并进行相应的更新。

适用场景:

  • 当主题状态的变化需要通知多个观察者,并且观察者的数量和身份可能会动态变化时,可以使用观察者模式。

  • 当观察者需要订阅和取消订阅主题的通知时,观察者模式是一个很好的选择。

  • 当希望在主题和观察者之间建立松散耦合关系,以便能够方便地添加、移除和修改观察者时,可以使用观察者模式。

发布-订阅模式:

发布-订阅模式是一种通过使用消息代理(称为发布者)来解耦发送者和接收者之间的通信。发布者将消息发送到一个或多个主题,而订阅者可以选择订阅感兴趣的主题并接收相应的消息。发布者和订阅者之间是一对多的关系。

适用场景:

  • 当需要在不同组件或模块之间进行松散耦合的通信时,发布-订阅模式是一个不错的选择。

  • 当发布者和订阅者之间的关系是动态的,并且它们可能随时加入或离开系统时,发布-订阅模式非常适用。

  • 当希望扩展和修改通信机制,例如引入消息代理或中间件来处理消息时,发布-订阅模式是一个强大的工具。

总结:

观察者模式适用于一对多的关系,主题和观察者之间的通信比较直接。而发布-订阅模式适用于发布者和订阅者之间的松散耦合通信,通过消息代理来进行中介。

最后更新于