title: 事件驱动
top: false
cover: true
author: 张文军
date: 2021-06-23 21:08:07
tags:

  • 事件驱动
  • 设计模式
  • Spring
    category:
  • 设计模式
    summary: Spring 事件使用-观察者、发布/订阅模式

Java快速开发学习

锁清秋

Spring 事件使用-观察者、发布/订阅模式

当系统从小到大演变过程,小的系统一个需求可能就2行代码就解决了,当不断添加新的需求或功能原有的代码就会越来越多,各种 if else语句各种循环,就算将一些功能抽离出使用新的方法,代码的可读性依然会随着系统的功能膨胀变得越来越低,扩展性、可维护性显得越来越重要,设计模式显得也就越来越重要。
本章介绍下,Spring 事件使用及观察者模式,如果对观察者模式有所了解可以直接调到Spring事件
Spring版本最好在4.2以上,低版本有些功能(注解)可能不支持。

观察者模式定义

一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
类图
在这里插入图片描述
对于刚刚接触设计模式的童鞋来说,看到它的定义和类图是一脸懵逼,千万不要死记硬背,就算记住了没有理解它的使用场景,在正式开发过程中根本想不到使用它,或变成了为了使用设计模式而使用设计模式。
MQ(消息队列中间件)就是这个设计模式的实现,如:RabbitMQ、ActiveMQ、Kafka等等;将一段消息发送到MQ继续处理其他逻辑,不需要关心消息是否被处理。

自定义观察者模式

自定义实现观察者模式,所有对象的创建交给Spring容器
主题类Subject

  1. package com.example.custom;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Component;
  4. import java.util.List;
  5. /**
  6. * 观察的主题,主题发生变化通知所用观察者
  7. * @author: sunshaoping
  8. * @date: Create by in 15:29 2021-6-13
  9. */
  10. @Component
  11. public class Subject {
  12. private final List<Observer> observerList;
  13. /**
  14. * Spring 容器中所有实现Observer接口的实例
  15. * @param observerList 所有实现Observer接口的实例
  16. */
  17. @Autowired
  18. public Subject(List<Observer> observerList) {
  19. this.observerList = observerList;
  20. }
  21. /**
  22. * 通知观察者
  23. * @param content 通知内容
  24. */
  25. public void notify(String content) {
  26. //通知全部观察者
  27. for (Observer observer : observerList) {
  28. observer.update(content);
  29. }
  30. }
  31. }

观察者接口Observer

  1. package com.example.custom;
  2. /**
  3. * 观察者/监听者
  4. * @author: sunshaoping
  5. * @date: Create by in 15:25 2021-6-13
  6. */
  7. public interface Observer {
  8. /**
  9. * 被观察者发布通知时,调用改方法
  10. * @param content 通知内容,此处使用的是String,它可以是任意对象
  11. */
  12. void update(String content);
  13. }

观察者1实现

  1. package com.example.custom;
  2. import org.springframework.stereotype.Component;
  3. /**
  4. * 观察者1
  5. * @author: sunshaoping
  6. * @date: Create by in 15:42 2021-6-13
  7. */
  8. @Component
  9. public class ObserverOne implements Observer {
  10. @Override
  11. public void update(String content) {
  12. System.out.println("我是观察者1,得到了通知:" + content);
  13. }
  14. }

观察者2实现

  1. package com.example.custom;
  2. import org.springframework.stereotype.Component;
  3. /**
  4. * 观察者2
  5. * @author: sunshaoping
  6. * @date: Create by in 15:42 2021-6-13
  7. */
  8. @Component
  9. public class ObserverTwo implements Observer {
  10. @Override
  11. public void update(String content) {
  12. System.out.println("我是观察者2,得到了通知:" + content);
  13. }
  14. }

发布通知测试

  1. package com.example.custom;
  2. import org.junit.Test;
  3. import org.junit.runner.RunWith;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.test.context.junit4.SpringRunner;
  7. import static org.junit.Assert.*;
  8. /**
  9. * @author: sunshaoping
  10. * @date: Create by in 15:48 2021-6-13
  11. */
  12. @RunWith(SpringRunner.class)
  13. @SpringBootTest
  14. public class SubjectTest {
  15. @Autowired
  16. Subject subject;
  17. @Test
  18. public void notify1() {
  19. subject.notify("您的支付宝到账100W ");
  20. subject.notify("您的工资发放 10W ");
  21. }
  22. }

这里只列举了一个主题和两个观察者,观察者可以任意增加,不影响现有的代码的逻辑,这就是设计模式的好处。

Java API观察者模式

其实java1.0就有了对观察者模式的实现,发展到现在基本上已经废弃了(java11已经废弃),但是并不影响我们使用它对观察者模式的讲解。
只需要实现一个观察者接口(Observer)和继承一个可观察类(Observable),分别对应自定义观察者模式 Observer(观察者)和Subject(主题),下面是简单的实现
可观察类:MyObservable

  1. package com.example.java;
  2. import java.util.Observable;
  3. public class MyObservable extends Observable {
  4. public MyObservable() {
  5. setChanged();
  6. }
  7. }

观察者:MyObserver

  1. package com.example.java;
  2. import java.util.Observable;
  3. import java.util.Observer;
  4. public class MyObserver implements Observer {
  5. @Override
  6. public void update(Observable o, Object arg) {
  7. System.out.println("java 观察者,收到 :" + arg);
  8. }
  9. }

测试类:Test

  1. package com.example.java;
  2. import java.util.Observable;
  3. public class Test {
  4. public static void main(String[] args) {
  5. Observable observable = new MyObservable();
  6. //添加观察者
  7. observable.addObserver(new MyObserver());
  8. //发布消息
  9. observable.notifyObservers("您的快递到了,请下楼领取 ");
  10. }
  11. }

是不是实现原理基本上是一样的。

Spring事件

正式进入主题Spring事件机制,
首先我们体验下我们订单为例

事件初体验

业务场景:监听订单创建时锁定指定商品
订单实体,省略get/set方法

  1. public class Order {
  2. /**
  3. * 订单编号
  4. */
  5. private String orderNo;
  6. /**
  7. * 订单状态
  8. */
  9. private String orderStatus;
  10. /**
  11. * 商品
  12. */
  13. private String goods;
  14. /**
  15. * 创建时间
  16. */
  17. private LocalDateTime createTime;

订单Service,保存订单逻辑处理

  1. @Service
  2. public class OrderService implements ApplicationEventPublisherAware {
  3. private ApplicationEventPublisher applicationEventPublisher;
  4. /**
  5. * 订单保存
  6. */
  7. public void save(Order order) {
  8. //生成订单号
  9. String orderNo = getOrderNo();
  10. order.setOrderNo(orderNo);
  11. order.setOrderStatus("待付款");
  12. order.setCreateTime( LocalDateTime.now());
  13. System.out.println("订单保存成功:" + order);
  14. //发布订单创建事件
  15. applicationEventPublisher.publishEvent(new OrderCreateEvent(this, order));
  16. }
  17. private String getOrderNo() {
  18. String millis = String.valueOf(System.currentTimeMillis());
  19. String str = millis.substring(millis.length() - 7, millis.length() - 1);
  20. return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + str;
  21. }
  22. @Override
  23. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
  24. this.applicationEventPublisher = applicationEventPublisher;
  25. }
  26. }

订单创建对象,实现ApplicationEvent接口

  1. public class OrderCreateEvent extends ApplicationEvent {
  2. private final Order order;
  3. public OrderCreateEvent(Object source, Order order) {
  4. super(source);
  5. this.order = order;
  6. }
  7. public Order getOrder() {
  8. return order;
  9. }
  10. }

订单事件监听,实现ApplicationListener接口,锁定商品

  1. @Component
  2. public class OrderCreateEventListener implements ApplicationListener<OrderCreateEvent> {
  3. @Override
  4. public void onApplicationEvent(OrderCreateEvent event) {
  5. System.out.printf("ApplicationListener 接口实现,订单号[%s]:,锁定商品[%s]\n",
  6. event.getOrder().getOrderNo(), event.getOrder().getGoods());
  7. }
  8. }

使用起来是不是很简单,而且它是Spring一个功能只要是Spring项目就可以使用(基本java项目都在使用Spring),这部分只展示了它的功能的小小一块。

简单的 Application Event

事件对象实现ApplicationEvent抽象类,如果上面的订单创建事件对象OrderCreateEvent

  1. public class OrderCreateEvent extends ApplicationEvent

监听类实现ApplicationListener<OrderCreateEvent>通过泛型的方式 OrderCreateEventListener
发布这个事件

  1. applicationEventPublisher.publishEvent(new OrderCreateEvent(this, order));

上面是最简单Spring 事件实现方式。

注解驱动 EventListener

事件监听不在用ApplicationListener接口,而是基于注解@EventListener驱动

  1. @Component
  2. public class OrderCreateEventListenerAnnotation {
  3. @EventListener
  4. public void orderCreateEvent(OrderCreateEvent event) {
  5. System.out.println("订单创建事件,@EventListener 注解驱动实现");
  6. }
  7. }

实现起来是不是很简单
如果在方法内没有使用事件对象,方法上也可以去掉它,但是要指定这个方法监听的是哪个事件类。如:

  1. @Component
  2. public class OrderCreateEventListenerAnnotation {
  3. @EventListener(OrderCreateEvent.class)
  4. public void orderCreateEvent() {
  5. System.out.println("订单创建事件,@EventListener 注解驱动实现");
  6. }
  7. }

条件事件

在一些时候可能只要满足某些条件才进行对事件监听,这时就可以用@EventListener#condition属性来指定条件,条件是一个SpEL表达式,关于SpEL请参考baeldung SpEL官网SpEL
只有订单状态是待付款才监听才有效。

  1. @EventListener(condition = "#event.order.orderStatus eq '待付款'")
  2. public void orderCreateEventCondition(OrderCreateEvent event) {
  3. System.out.println("订单创建事件,@EventListener 注解驱动实现");
  4. }

异步事件

覆盖默认ApplicationEventMulticaster,通过源码就可以知道它的原理
AbstractApplicationContext#initApplicationEventMulticaster初始化方法

  1. /**
  2. * Initialize the ApplicationEventMulticaster.
  3. * Uses SimpleApplicationEventMulticaster if none defined in the context.
  4. * @see org.springframework.context.event.SimpleApplicationEventMulticaster
  5. */
  6. protected void initApplicationEventMulticaster() {
  7. ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  8. //IOC容器中存在
  9. //beanName=APPLICATION_EVENT_MULTICASTER_BEAN_NAME("applicationEventMulticaster")
  10. //使用容器中的对象
  11. if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
  12. this.applicationEventMulticaster =
  13. beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
  14. if (logger.isTraceEnabled()) {
  15. logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
  16. }
  17. }
  18. else {
  19. //否则使用 SimpleApplicationEventMulticaster
  20. this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
  21. beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
  22. if (logger.isTraceEnabled()) {
  23. logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
  24. "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
  25. }
  26. }
  27. }

通过上面一段源码,我们就可以自定义实现ApplicationEventMulticaster接口,只需要向IOC容器中注册一个beanName=”applicationEventMulticaster”实现ApplicationEventMulticaster接口的对象就可以覆盖默认事件发布规则了。

  1. @Configuration
  2. public class AsynchronousSpringEventsConfig {
  3. @Bean(name = "applicationEventMulticaster")
  4. public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
  5. SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
  6. //设置任务执行器
  7. eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
  8. return eventMulticaster;
  9. }
  10. }

上面的代码就实现了异步发布事件,这个是全局定义所有Spring 事件都会变成异步;
如果想要一部分事件是异步一部分是同步的就不满足要求了,接下来我们讲一下针对一个事件监听或事件实现异步支持。

异步事件Async

结合@Async注解实现异步事件,使用很简单,只需要在相关方法或类上添加@Async注解,使用在方法上只针对这一个方法异步调用,使用在类上所有的方法都是异步调用;必须使用注解@EnableAsync开启异步功能
开启异步

  1. @EnableAsync
  2. @SpringBootApplication
  3. public class SpringEventExampleApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(SpringEventExampleApplication.class, args);
  6. }
  7. }

异步监听事件

  1. @Async
  2. @EventListener
  3. public void orderCreateEventAsync(OrderCreateEvent event) {
  4. System.out.println("订单创建事件,@EventListener 注解驱动实现");
  5. }

泛型支持

也可以自定义泛型类实现事件调度。
让我们创建一个泛型事件类,没有继承任何父类或接口。
泛型事件类:

  1. public class GenericEvent<T> {
  2. private final T data;
  3. public GenericEvent(T data) {
  4. this.data = data;
  5. }
  6. public T getData() {
  7. return data;
  8. }
  9. }

泛型事件用注解驱动监听 @EventListener

  1. @EventListener
  2. public void orderListener(GenericEvent<Order> event) {
  3. System.out.println("通用泛型事件监听,订单");
  4. }

事件对象可以是任意对象,也可以不是泛型类型,比如我们直接发布一个Order监听一个Order
Order发布

  1. applicationEventPublisher.publishEvent(order);

Order监听

  1. @EventListener
  2. public void order(Order order) {
  3. System.out.println("监听一个订单");
  4. }

这样就不用专门创建一个事件对象,是不是更简单了。

事物事件@TransactionalEventListener

从Spring4.2开始提供了一个新的事件监听注解@TransactionalEventListener,它是@EventListener的扩展,允许将事件的监听绑定到事物的一个阶段。有以下四个阶段:
AFTER_COMMIT(默认值)事务成功提交时触发事件
AFTER_ROLLBACK - 事务回滚
AFTER_COMPLETION - 事务已完成(AFTER_COMMIT 或 AFTER_ROLLBACK)
BEFORE_COMMIT 在事务提交之前触发事件

  1. @TransactionalEventListener(phase = BEFORE_COMMIT)
  2. public void txEvent(Order order) {
  3. System.out.println("事物监听");
  4. }

仅当存在事件生成器正在运行且即将提交的事务时,才会调用此侦听器。
并且,如果没有正在运行的事务,则根本不发送事件,除非我们通过将fallbackExecution 属性设置为true来覆盖它 ,即TransactionalEventListener(fallbackExecution = true)。

新事件继续传播

当我们监听一个事件处理完成时,还需要发布另一个事件,一般我们想到的是调用ApplicationEventPublisher#publishEvent发布事件方法,但Spring提供了另一种更加灵活的新的事件继续传播机制,监听方法返回一个事件,也就是方法的返回值就是一个事件对象。
监听方法方法返回一个新的事件

  1. @EventListener
  2. public OrderCreateEvent orderReturnEvent(Order order) {
  3. System.out.println("监听一个订单,返回一个新的事件 OrderCreateEvent");
  4. return new OrderCreateEvent(this,order);
  5. }

监听方法方法返回多个事件-集合

  1. @EventListener
  2. public Collection<OrderCreateEvent> orderReturnListEvent(Order order) {
  3. System.out.println("监听一个订单,返回集合的事件 OrderCreateEvent");
  4. return Collections.singleton(new OrderCreateEvent(this, order));
  5. }

监听方法方法返回多个事件-数组

  1. @EventListener
  2. public Object[] orderReturnArrayEvent(Order order) {
  3. System.out.println("监听一个订单,返回数组的事件 OrderCreateEvent");
  4. return new OrderCreateEvent[]{new OrderCreateEvent(this, order), new OrderCreateEvent(this, order)};
  5. }

注意返回集合事件时,集合内事件类型可以不同。

总结
本章节从观察者模式为入口点,分别以自定义、java API、Spring 事件介绍了他们对观察者模式的实现。
Spring事件各种发布和监听的方式,我们可以根据业务情况任意选择。
如果你的项目Spring版本低于4.2基于注解驱动方式则不适合,只能继承事件类或实现监听接口的方式。
原文链接:https://blog.csdn.net/sun_shaoping/article/details/84067446