Bootstrap

Spring事件驱动

前言

spring的事件驱动是观察者模式的一种实现,其目的是将耦合的代码解耦,方便功能的扩展和维护。

观察者模式简介

一种行为型设计模式,定义了一种一对多的依赖关系,当一个对象的状态发生变化时,通过发布事件的形式通知所有的观察者。观察者模式的核心角色有:

  • 主题,也就是被观察者,维护了一个观察者列表,定义了观察者的注册、删除,以及通知方法
  • 观察者,接收主题通知的对象,定义了接收到通知时要调用的更新方法
  • 具体主题,主题的具体实现,实现了注册、删除和通知方法
  • 具体观察者 ,观察者的具体实现,实现了更新方法

Spring中的ApplicationListener对应观察者,ApplicationListener抽象类的实现类对应具体观察者。主题的角色被进一步拆分,由ApplicationEventMulticaster维护观察者列表和提供观察者的注册、删除、通知方法,在业务对象的状态发生变化时,通过ApplicationEventPublisher委托广播器进行事件通知。
Spring还定义了ApplicationEvent事件对象作为数据载体来传递信息。

使用案例

  1. 定义业务状态对应的ApplicationEvent事件
public class StatusChangeEvent extends ApplicationEvent {
   public StatusChangeEvent(DataObject data) {
       super(data);
   }
}

2.在需要进行状态通知的业务对象里注入ApplicationEventPublisher,在状态变更时发布事件。ApplicationContext容器本身实现了ApplicationEventPublisher接口,在默认情况下注入的就是Spring容器

@Component
public class BusinessObject {
    // 不推荐
    // @Autowired
    // private ApplicationContext applicationContext;

    // 推荐,逻辑更清晰
    @Autowired
    private ApplicationEventPublisher publisher;

    public void businessMethod() {
        publisher.publishEvent(new StatusChangeEvent(new DataObject()));
    }
}

3.定义ApplicationListener,指定监听的业务事件,并实现事件的处理逻辑。可以指定一个类为监听者,也可以指定一个方法。
定义监听器类

@Component
// 需要实现ApplicationListener接口
public class BusinessEventListener implements ApplicationListener<StatusChangeEvent> {
    @Override
    public void onApplicationEvent(StatusChangeEvent event) {
        // 实现事件处理逻辑
    }
}

指定一个方法为listener

@Component
public class BusinessObject2 {
    @EventListener
    public void businessMethod(StatusChangeEvent event) {
        DataObject object = (DataObject) event.getSource();
        System.out.println(object.getMsg());
    }
}

spring提供了默认的ApplicationEventMulticaster——SimpleApplicationEventMulticaster

使用进阶

异步执行

默认情况下监听方法是同步执行的,但不影响主流程的处理逻辑其实可以异步处理,以降低主流程耗时

  1. 在springboot的启动类上添加@EnableAsync注解
  2. 监听方法上添加@Async注解并指定线程池
@Async("businessExecutor")
@EventListener
public void asyncMethod(StatusChangeEvent event) {
    DataObject object = (DataObject) event.getSource();
    System.out.println(Thread.currentThread().getId() + ":" + object.getMsg());
}

条件监听

在监听的事件的属性满足一定条件时才执行事件处理逻辑的场景,可以使用注解的condition属性开启条件监听,方便优雅的实现。

@Component
public class ConditionalListener {
    @EventListener(condition = "#event.source.msg=='hello'")
    public void conditionalMethod(StatusChangeEvent event) {
        DataObject source = (DataObject) event.getSource();
        System.out.println("监听成功:msg="+source.getMsg());
    }
}

控制监听器执行顺序

有时对一个事件的处理,多个监听器需要按一定顺序执行。此时可以在监听器类或方法上添加@Order注解来控制监听器的执行顺序。设定的值越小越先执行。

@Component
public class OrderListener {
    @EventListener
    @Order(3)
    public void listen3(OrderEvent orderEvent) {
        System.out.println("listener 3");
    }

    @EventListener
    @Order(1)
    public void listen1(OrderEvent orderEvent) {
        System.out.println("listener 1");
    }

    @EventListener
    @Order(2)
    public void listen2(OrderEvent orderEvent) {
        System.out.println("listener 2");
    }
}

应用案例

spring使用事件机制提供扩展点

在spring(abstractApplicationContext#refresh)应用容器刷新完成时(finishRefresh),会广播ContextRefreshedEvent事件,通过实现对应的事件监听器就可插入自定义的扩展逻辑。

sofaboot的健康检查

sofaboot作为蚂蚁内部对springboot的增强框架,优势之一是提供了健康检测机制。当healthcheck-sofa-boot包里定义的ReadinessCheckListener监听到ContextRefreshedEvent事件后,会进行健康检查,当有任何检查失败就会抛异常启动失败。
ReadinessCheckListener的事件处理

public class ReadinessCheckListener implements ApplicationContextAware, PriorityOrdered, ApplicationListener<ContextRefreshedEvent>, InitializingBean {
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (this.applicationContext.equals(event.getApplicationContext())) {
            // 初始化,从容器里取HealthChecker
            this.healthCheckerProcessor.init();
            this.healthIndicatorProcessor.init();
            this.afterReadinessCheckCallbackProcessor.init();
            // 执行所有的HealthChecker的检查方法
            this.readinessHealthCheck();
            this.readinessCheckFinish = true;
        }
    }
}

注意事项

同步执行场景下,多个监听器是链式处理的,一个监听器执行失败并抛出异常的话,会导致后续监听器无法监听到事件。此时可以:

  • 监听器使用try catch处理异常,不让异常抛出
  • 自定义事件广播器并设置一个ErrorHandler用来处理异常,这个广播器的beanName一定要是applicationEventMulticaster
@Bean("applicationEventMulticaster")
public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster() {
    SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
    simpleApplicationEventMulticaster.setErrorHandler(t -> {
        System.out.println("捕获到了异常:"+t.getMessage());
    });
    return simpleApplicationEventMulticaster;
}
;