一文读懂Spring事件机制

IT知识
409
0
0
2022-12-20

一文读懂Spring事件机制

Spring 事件机制是为同一ApplicationContext中多个Bean之间的通信而设计的,它是观察者模式的一个典型应用。

观察者模式在生活中随处可见,比如:在安装有交通信号灯的路口,行人需要实时观察信号灯的变化,从而判断是否需要前进、缓行或等待;而信号灯只负责发布信号变更事件,并不会干预行人,也就是说信号灯与行人之间是松耦合的关系。

为什么要搞清楚 Spring 事件机制呢?因为这玩意儿在 Spring 内部应用广泛,不搞清楚的话,看 Spring 相关源码会是一个拦路虎;此外,也可以在实际工作中解决相关问题,比如:笔者在工作中曾借助它来实现流程节点的自动流转,这些业务流程往往很长,但只有少数流程节点需要客户手动触发完成,其余节点需要自动流转。

1 从实战出发

聊到事件机制,那就不得不提事件处理模型三要素了,即事件源、事件和事件监听器。

1.1 定义事件

在 Spring 4.2 之前,所有事件均需要继承自ApplicationEvent,而 ApplicationEvent 又继承自java.util.EventObject;但在 Spring 4.2 之后,并不强制要求自定义事件继承 ApplicationEvent,因为 Spring 会将开发者自定义的事件类包装为一个PayloadApplicationEvent,其实,PayloadApplicationEvent 同样继承自 ApplicationEvent。

img

1.2 定义事件监听器

// 事件监听器,即行人
@Component
@Slf4j
public class CustomTrafficSignalListener implements ApplicationListener<TrafficSignalEvent> {
    @Override 
    public void onApplicationEvent(TrafficSignalEvent trafficSignalEvent) {
        if (trafficSignalEvent instanceof RedSignalEvent) {
            log.info("红灯亮,请所有行人和车辆停止前行");
        } else if (trafficSignalEvent instanceof GreenSignalEvent) {
            log.info("绿灯亮,请所有行人和车辆前行");
        } else if (trafficSignalEvent instanceof YellowSignalEvent) {
            log.info("黄灯亮,请所有行人和车辆缓行");
        } else {
            throw new IllegalStateException("未知的信号灯事件");
        }
    }
}

Spring 4.2 提供了org.springframework.context.event.EventListener注解,不再需要实现org.springframework.context.ApplicationListener接口。相信大家在实际工作中更倾向于使用@EventListener的方式来定义事件监听器。

// 事件监听器,即行人
@Component
@Slf4j
public class FlexibleTrafficSignalListener {
    @EventListener 
    public void onTrafficSignalEvent(TrafficSignalEvent event) {
        if (event instanceof RedSignalEvent) {
            log.info("红灯亮,请所有行人和车辆停止前行");
        } else if (event instanceof GreenSignalEvent) {
            log.info("绿灯亮,请所有行人和车辆前行");
        } else if (event instanceof YellowSignalEvent) {
            log.info("黄灯亮,请所有行人和车辆缓行");
        } else {
            throw new IllegalStateException("未知的信号灯事件");
        }
    }
}

1.3 定义事件源

// 交通信号灯,即事件源
@Component
public class TrafficSignal implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;

    @Override 
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void doSignal() {
        TrafficSignalEvent redSignalEvent = new RedSignalEvent(this);
        applicationEventPublisher.publishEvent(redSignalEvent);
    }
}

1.4 运行结果

CustomTrafficSignalListener

2021-08-16 20:59:19.728  INFO 11908 --- [main] p.d.e.e.CustomTrafficSignalListener      : 红灯亮,请所有行人和车辆停止前行

FlexibleTrafficSignalListener

2021-08-16 20:59:22.135  INFO 17653 --- [main] p.d.e.e.FlexibleTrafficSignalListener    : 红灯亮,请所有行人和车辆停止前行

2 异步事件监听器

默认情况下,事件监听器基于 同步机制 来处理事件,这意味着ApplicationEventPublisherpublishEvent()方法会阻塞,直到所有事件监听器都处理完毕。如果你想指定某一事件监听器基于 异步机制 来处理事件,可以使用@Async注解。当然,也可以通过配置自定义的ApplicationEventMulticaster使得所有事件监听器都基于异步机制处理事件。值得一提的是,全局异步监听所涉及的这一自定义ApplicationEventMulticaster有个坑,即该Bean的名称必须是applicationEventMulticaster,否则不会生效,具体原因下面会分析。

2.1 配置自定义事件广播器

@Configuration
public class ApplicationEventMulticasterConfig {
    @Bean 
    public ApplicationEventMulticaster applicationEventMulticaster() {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("event-pool-%d").build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                0,
                16,
                0L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(24),
                namedThreadFactory,
                new ThreadPoolExecutor.AbortPolicy()
        );
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        simpleApplicationEventMulticaster.setTaskExecutor(threadPoolExecutor);
        return simpleApplicationEventMulticaster;
    }
}

2.2 运行结果

2021-08-16 22:05:50.053  INFO 13652 --- [event-pool-11] p.d.e.e.FlexibleTrafficSignalListener    : 红灯亮,请所有行人和车辆停止前行

3 由Spring所发布的预定义事件

Spring 提供了一些预定义的事件,如下图所示:

img

但有些事件在 ApplicationContext 创建之前就已触发,该如何观测它们呢?

3.1 定义事件监听器

@Slf4j
public class SpringBuiltInEventsListener implements ApplicationListener<ApplicationEvent> {
    @Override 
    public void onApplicationEvent(ApplicationEvent event) {
        log.info("event = {}", event);
    }
}

3.2 向SpringApplication注册事件监听器

@SpringBootApplication(scanBasePackages = "pers.duxiaotou")
public class DuXiaoTouSpringAopApp {
    public static void main(String[] args) throws Throwable {
        SpringApplication springApplication = new SpringApplication(DuXiaoTouSpringAopApp.class);
        springApplication.addListeners(new SpringBuiltInEventsListener());
        springApplication.run(args);
    }
}

3.3 运行结果

2021-08-18 17:14:43.182  INFO 15624 --- [           main] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:43.291  INFO 15624 --- [           main] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.context.event.ApplicationContextInitializedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:43.417  INFO 15624 --- [           main] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.context.event.ApplicationPreparedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:45.865  INFO 15624 --- [   event-pool-0] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@4ca6ee9b]
2021-08-18 17:14:45.872  INFO 15624 --- [   event-pool-0] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54d18072, started on Wed Aug 18 17:14:43 CST 2021]
2021-08-18 17:14:45.873  INFO 15624 --- [   event-pool-0] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:45.873  INFO 15624 --- [   event-pool-0] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54d18072, started on Wed Aug 18 17:14:43 CST 2021]
2021-08-18 17:14:45.875  INFO 15624 --- [   event-pool-1] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:45.875  INFO 15624 --- [   event-pool-1] p.d.e.e.SpringBuiltInEventsListener      : event = org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54d18072, started on Wed Aug 18 17:14:43 CST 2021]

4 实现原理

在 Spring 事件机制中,ApplicationEventPublisher负责发布事件,其只定义了两个publishEvent()方法,如下:

public interface ApplicationEventPublisher {
 default void publishEvent(ApplicationEvent event) {
  publishEvent((Object) event);
 }
 void publishEvent(Object event);
}

img

ApplicationEventPublisher 的实现类几乎都以ApplicationContext结尾,这难道有什么玄机吗?Spring 事件机制是为同一ApplicationContext中多个Bean之间的通信而设计的,而 ApplicationContext 在 Spring 中扮演IoC容器的角色,负责Bean全生命周期管理,那发布事件当然交由ApplicationContext最合适了啊;其中最为重要的非AbstractApplicationContext莫属!废话不多说,上代码:

public abstract class AbstractApplicationContext 
        extends DefaultResourceLoader 
        implements ConfigurableApplicationContext {
    // 事件广播器的名称必须指定为applicationEventMulticaster,源码中就是这么限定的 
    // 如果不按规矩来,那自定义的事件广播器屁用没有 
    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

    // 事件广播器,负责向所有事件监听器广播事件 
    private ApplicationEventMulticaster applicationEventMulticaster;

    private final Object startupShutdownMonitor = new Object();

    @Override 
    public void publishEvent(ApplicationEvent event) {
        publishEvent(event, null);
    }
    @Override 
    public void publishEvent(Object event) {
        publishEvent(event, null);
    }

    // 发布事件核心逻辑 
    protected void publishEvent(Object event, ResolvableType eventType) {
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        } else {
            // 如果所发布的事件不是ApplicationEvent类型,那么默认将其包装为ApplicationEvent类型
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }
        // 通过事件广播器广播事件,广播给谁?事件监听器 
        this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);
    }

    /**
     * 这个方法容易忽略,请重点关注以下两个方法,相信看了之后应该能解决你的疑惑···
     * 1) initApplicationEventMulticaster()
     * 2) registerListeners()
     */ 
    @Override 
    public void refresh() {
        synchronized (this.startupShutdownMonitor) {
            // ...

            // 初始化事件广播器 
            initApplicationEventMulticaster();
            // 注册事件监听器 
            registerListeners();

            // ...
        }
    }

    // 初始化事件广播器 
    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            // 如果开发者自定义了名称为applicationEventMulticaster的事件广播器,则优先使用开发者自定义的 
            this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        } else {
            // 如果开发者没有自定义或者定义了名称不是applicationEventMulticaster的事件广播器,则默认使用SimpleApplicationEventMulticaster 
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        }
    }

    // 注册事件监听器 
    protected void registerListeners() {
        // 将事件监听器注册到事件广播器中 
        // 但这里的事件监听器专指静态事件监听器,即META-INF下spring.factories配置文件中的事件监听器 
        // 比如:spring-boot、jasypt-spring-boot等模块中均配置了静态的事件监听器 
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            this.applicationEventMulticaster.addApplicationListener(listener);
        }
        
        // 同样是将事件监听器注册到事件广播器中 
        // 但这里主要指那些实现ApplicationListener接口的事件监听器哦 
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            this.applicationEventMulticaster.addApplicationListenerBean(listenerBeanName);
        }
    }
}

从 AbstractApplicationContext 的源码来看,主要交代了两件事:

  1. AbstractApplicationContext 是实现事件发布的真正大佬,但发布事件并没有直接与事件监听器耦合,而是通过事件广播器来桥接;
  2. 当启动 Spring Boot 时会触发针对 ApplicationContext 的refresh()操作,而refresh()首先会初始化事件广播器,然后注册事件监听器;注意:registerListeners()中所涉及的getApplicationListeners()方法其实只是直接拿到事件监听器列表而已,真正从spring.factories配置文件中读取事件监听器的触发时机是在实例化SpringApplication的时候。眼尖的朋友可能会问:由@EventListener定义的事件监听器是如何注册到事件广播器中的呢?的确,这里不涉及基于注解模型的事件监听器的注册工作,这些事件监听器由EventListenerMethodProcessor来完成注册工作,最终会将其封装为ApplicationListenerMethodAdapter类型的监听器,但依然是ApplicationListener的子类。

接下来,自然要来认识一下ApplicationEventMulticaster大佬了。事件广播器不仅负责广播事件,同时主导Spring和开发者所定义的事件监听器的维护工作。

public interface ApplicationEventMulticaster {
    void addApplicationListener(ApplicationListener<?> listener);
    void addApplicationListenerBean(String listenerBeanName);
    void removeApplicationListener(ApplicationListener<?> listener);
    void removeApplicationListenerBean(String listenerBeanName);
    void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);
    void removeApplicationListenerBeans(Predicate<String> predicate);
    void removeAllListeners();
    void multicastEvent(ApplicationEvent event);
    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

img

Spring 事件机制默认采用SimpleApplicationEventMulticaster来作为事件广播器,其源码过于直白,这里就直接奉上源码不再解读了。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    // 线程池,用于实现异步监听器 
    private Executor taskExecutor;

    private ErrorHandler errorHandler;

    @Override 
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event));
    }

    @Override 
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event);
        Executor executor = getTaskExecutor();
        // 事件广播器会遍历所有事件监听器,逐一向其发布事件 
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            } else {
                invokeListener(listener, event);
            }
        }
    }

    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        } else {
            doInvokeListener(listener, event);
        }
    }

    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        } catch (ClassCastException ex) {
            throw ex;
        }
    }
}

5 总结

img

6 参考文档

  1. https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-events
  2. https://refactoring.guru/design-patterns/observer