SpringBoot中事件广播体系梳理

简介: SpringBoot中事件广播体系梳理

上文Spring中事件监听(通知)机制详解与实践我们研究是Spring中的事件机制原理并进行了实践,本文我们梳理一下SpringBoot中事件广播的几个分支。

【1】持有分支

事件广播是使用SimpleApplicationEventMulticaster这个应用事件广播器进行处理的。所以我们只需要看这个实例被哪些类持有。

① AbstractApplicationContext

也就是应用的"高级"容器,内部持有了基础的IOC容器DefaultListableBeanFactory。如果是我们自定义事件,那么也是通过这个容器持有的事件广播器进行广播的。

@Nullable
private ApplicationEventMulticaster applicationEventMulticaster;

如下所示,在refresh过程中会为应用上下文实例化一个事件广播器。

② EventPublishingRunListener

其实现了SpringApplicationRunListener接口,用来发布SpringApplicationEvent事件(通过其持有的事件广播器)。如下七种事件均是SpringApplicationEvent事件体系。

  • starting()方法广播ApplicationStartingEvent事件
  • environmentPrepared()方法广播ApplicationEnvironmentPreparedEvent事件
  • contextPrepared()方法广播ApplicationContextInitializedEvent事件
  • contextLoaded()方法广播ApplicationPreparedEvent事件
  • started()方法广播ApplicationStartedEvent事件
  • running()方法广播ApplicationReadyEvent事件
  • failed()方法广播ApplicationFailedEvent事件

其中如下三个在SpringApplication的run方法主流程中:

listeners.starting();
listeners.started(context);
listeners.running(context);

environmentPrepared方法在创建并配置环境的过程中,contextPrepared和contextLoaded则是在prepareContext过程中。

如下构造方法所示,EventPublishingRunListener持有了SimpleApplicationEventMulticaster 和SpringApplication 。SimpleApplicationEventMulticaster 用来实现事件广播,SpringApplication 则是将其监听器添加到事件广播的成员中。

private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
  this.application = application;
  this.args = args;
  this.initialMulticaster = new SimpleApplicationEventMulticaster();
  for (ApplicationListener<?> listener : application.getListeners()) {
    this.initialMulticaster.addApplicationListener(listener);
  }
}

③ DelegatingApplicationListener

DelegatingApplicationListener的核心成员和构造方法如下所示,维护了一个事件广播器,对ApplicationEnvironmentPreparedEvent事件感兴趣。

private SimpleApplicationEventMulticaster multicaster;
@Override
public void onApplicationEvent(ApplicationEvent event) {
  if (event instanceof ApplicationEnvironmentPreparedEvent) {
    List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
        ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
    if (delegates.isEmpty()) {
      return;
    }
    this.multicaster = new SimpleApplicationEventMulticaster();
    for (ApplicationListener<ApplicationEvent> listener : delegates) {
      this.multicaster.addApplicationListener(listener);
    }
  }
  if (this.multicaster != null) {
    this.multicaster.multicastEvent(event);
  }
}

【2】ApplicationListener

由应用程序事件侦听器实现的接口,是一个功能性接口,同时是基于观察器设计模式的标准{java.util.EventListener}接口。

从Spring 3.0开始,ApplicationListener可以通用地声明它感兴趣的事件类型。当向Spring ApplicationContext 注册时,事件将被相应地过滤,侦听器仅当匹配事件对象时被调用。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
   // 处理applicationEvent
  void onApplicationEvent(E event);
}

我们简单梳理一些常见的内置监听器。

① RestartApplicationListener

如下所示,其贯穿整个应用的生命周期并通过一个Restarter实例来完成不同的动作。

@Override
public void onApplicationEvent(ApplicationEvent event) {
  if (event instanceof ApplicationStartingEvent) {
    onApplicationStartingEvent((ApplicationStartingEvent) event);
  }
  if (event instanceof ApplicationPreparedEvent) {
    onApplicationPreparedEvent((ApplicationPreparedEvent) event);
  }
  if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) {
    Restarter.getInstance().finish();
  }
  if (event instanceof ApplicationFailedEvent) {
    onApplicationFailedEvent((ApplicationFailedEvent) event);
  }
}

ApplicationStartingEvent事件触发onApplicationStartingEvent方法,用来完成Restarter的实例化。

ApplicationPreparedEvent事件触发onApplicationPreparedEvent方法,触发Restarter实例的prepare方法,最终为applicationContext设置ClassLoaderFilesResourcePatternResolver这样一个ResourceLoader,并为Restarter实例维护的rootContexts添加当前applicationContext。

ApplicationReadyEvent事件会触发Restarter实例的finish方法,修改Restarter实例的finished标识为true。

ApplicationFailedEvent事件除了做ApplicationReadyEvent事件一样的操作完,还会触发onApplicationFailedEvent方法,进而触发Restarter实例的remove方法,从Restarter实例维护的rootContexts中移除当前applicationContext。

② BackgroundPreinitializer

当listener.starting方法触发的时候,广播ApplicationStartingEvent事件就会触发到这个监听器的执行。

ApplicationStartingEvent事件广播时,默认情况下BackgroundPreinitializer会以后台多线程方式实例化一些"基础设置服务类"。当ApplicationReadyEvent或者ApplicationFailedEvent事件广播时,如果这里preinitializationStarted为true,那么就触发闭锁preinitializationComplete的等待方法,直到前面提到的多线程执行完。其实通常来讲,这里不会等待,只是一种保障措施。毕竟ApplicationStartingEvent离ApplicationReadyEvent蛮远的。

@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
//指示Spring Boot如何运行预初始化的系统属性。
//当属性设置为{true}时,不会发生预初始化,并且每个项都会根据需要在前台初始化。
//当属性为{@code false}(默认值)时,预初始化在后台的单独线程中运行。
  public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";
  private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);
  // 闭锁,用来控制线程的执行
  private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);
  @Override
  public void onApplicationEvent(SpringApplicationEvent event) {
    if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
        && event instanceof ApplicationStartingEvent && multipleProcessors()
        && preinitializationStarted.compareAndSet(false, true)) {
      performPreinitialization();
    }
    if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
        && preinitializationStarted.get()) {
      try {
      // 等待,直到闭锁持有计数为0或者发生了线程中断
        preinitializationComplete.await();
      }
      catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
      }
    }
  }
  private boolean multipleProcessors() {
    return Runtime.getRuntime().availableProcessors() > 1;
  }

如上所示,当进行了条件判断后会触发performPreinitialization方法进行一些实例的初始化。当然事件为ApplicationReadyEventApplicationFailedEventpreinitializationStarted这个原子对象值为true时,会触发闭锁的await()方法,直到performPreinitialization方法中的线程任务执行完毕。

而performPreinitialization()方法如下所示,其首先触发了五个线程,然后

preinitializationComplete.countDown()释放闭锁资源。

private void performPreinitialization() {
  try {
    Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
        runSafely(new ConversionServiceInitializer());
        runSafely(new ValidationInitializer());
        runSafely(new MessageConverterInitializer());
        runSafely(new JacksonInitializer());
        runSafely(new CharsetInitializer());
        preinitializationComplete.countDown();
      }
      public void runSafely(Runnable runnable) {
        try {
          runnable.run();
        }
        catch (Throwable ex) {
          // Ignore
        }
      }
    }, "background-preinit");
    thread.start();
  }
  catch (Exception ex) {
    preinitializationComplete.countDown();
  }
}

ConversionServiceInitializer主要用来实例化DefaultFormattingConversionService,其实例化时会实例化并注册一些DefaultConverters和DefaultFormatters。

ValidationInitializer主要用来针对javax.validation进行ValidatorFactoryValidator创建与配置。

MessageConverterInitializer主要用来实例化AllEncompassingFormHttpMessageConverter,其构造方法中会实例化一些MessageConverter并添加到FormHttpMessageConverter.partConverters集合中


JacksonInitializer主要针对Jackson的一些配置,这里会实例化Jackson2ObjectMapperBuilder并进行配置。


CharsetInitializer则会触发StandardCharsets.UTF_8.name();,会导致StandardCharsets这个final类的加载和常量赋值。


上面几个XXXXInitializer均实现了Runnable接口。关于CountDownLatch 更多信息可以参考博文:多线程并发之CountDownLatch(闭锁)使用详解

③ ConfigFileApplicationListener

其继承树示意图如下所示,除了是一个ApplicationListener外,其还是一个EnvironmentPostProcessor。其对ApplicationEnvironmentPreparedEvent

ApplicationPreparedEvent事件感兴趣。

onApplicationEnvironmentPreparedEvent

onApplicationEnvironmentPreparedEvent方法如下所示,会通过

SpringFactoriesLoader.loadFactories方法获取配置的EnvironmentPostProcessor,然后添加自身后进行遍历触发每一个EnvironmentPostProcessorpostProcessEnvironment方法。

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 获取配置的EnvironmentPostProcessor
  List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
  // 添加自身
  postProcessors.add(this);
  AnnotationAwareOrderComparator.sort(postProcessors);
  // 循环遍历触发每个的postProcessEnvironment方法
  for (EnvironmentPostProcessor postProcessor : postProcessors) {
    postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
  }
}

postProcessEnvironment会做什么呢?以ConfigFileApplicationListener为例,如下所示

// 添加PropertySource
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
  addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// 添加random PropertySource
  RandomValuePropertySource.addToEnvironment(environment);
  new Loader(environment, resourceLoader).load();
}

其主要用来干嘛呢,简单来讲就是将我们的配置文件扫描形成PropertySource放到环境中。比如本文这里的application.properties:

onApplicationPreparedEvent

如下所示,其会向应用上下文的List<BeanFactoryPostProcessor> beanFactoryPostProcessors添加PropertySourceOrderingPostProcessor

private void onApplicationPreparedEvent(ApplicationEvent event) {
  this.logger.switchTo(ConfigFileApplicationListener.class);
  addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
protected void addPostProcessors(ConfigurableApplicationContext context) {
  context.addBeanFactoryPostProcessor(new PropertySourceOrderingPostProcessor(context));
}

④ ClasspathLoggingApplicationListener

其对ApplicationEnvironmentPreparedEvent和ApplicationFailedEvent事件感兴趣,用来打印线程上下文类加载器( the thread context class loader (TCCL))的classpath,在debug 级别。

@Override
public void onApplicationEvent(ApplicationEvent event) {
  if (logger.isDebugEnabled()) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
      logger.debug("Application started with classpath: " + getClasspath());
    }
    else if (event instanceof ApplicationFailedEvent) {
      logger.debug("Application failed to start with classpath: " + getClasspath());
    }
  }
}

⑤ DelegatingApplicationListener

一个应用了委派策略的监听器,对ApplicationEvent事件感兴趣。当

ApplicationEnvironmentPreparedEvent事件广播时,其会尝试从环境里面获取context.listener.classes配置信息,如果其配置的监听器不为空,则实例化

SimpleApplicationEventMulticaster并广播事件给这些监听器。

如果没有配置context.listener.classes,那么该监听器在整个应用中毫无作为。

@Override
public void onApplicationEvent(ApplicationEvent event) {
  if (event instanceof ApplicationEnvironmentPreparedEvent) {
    List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
        ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
    if (delegates.isEmpty()) {
      return;
    }
    this.multicaster = new SimpleApplicationEventMulticaster();
    for (ApplicationListener<ApplicationEvent> listener : delegates) {
      this.multicaster.addApplicationListener(listener);
    }
  }
  if (this.multicaster != null) {
    this.multicaster.multicastEvent(event);
  }
}

如果配置了context.listener.classes,那么将会将事件(ApplicationEvent类型)广播给这些监听器。也就是说在application.yml或者在application.properties配置文件中通过

context.listener.classes配置监听类,但是需要注意,这种配置无法监听ApplicationStartingEvent事件。

⑥ ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>,

ApplicationListener<WebServerInitializedEvent>,也就是其不单单是个监听器,还是个应用上下文初始化器。

在prepareContext方法中会触发初始化器的初始化方法,这里如下所示会将自身作为监听器添加到应用上下文中。

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
  applicationContext.addApplicationListener(this);
}

作为事件监听器时,其会创建server.ports这样一个MapPropertySource,并放入属性local.server.port与应用端口。


目录
相关文章
|
5天前
|
Java 容器
SpringBoot3 事件和监听器
SpringBoot3 事件和监听器
|
5天前
|
NoSQL Java Redis
Spring boot 实现监听 Redis key 失效事件
【2月更文挑战第2天】 Spring boot 实现监听 Redis key 失效事件
123 0
|
8月前
|
Java Spring
ddd 领域事件 springboot 代码案例
ddd 领域事件 springboot 代码案例
231 0
|
5天前
|
XML Java 数据格式
[springboot bug] mac 文件读取灵异事件
[springboot bug] mac 文件读取灵异事件
|
8月前
|
安全 Java UED
SpringBoot 如何使用 @Async 注解处理异步事件
SpringBoot 如何使用 @Async 注解处理异步事件
|
11月前
|
消息中间件 运维 Java
SpringBoot开发秘籍 - 事件异步处理
SpringBoot开发秘籍 - 事件异步处理
170 0
|
Java Spring
Java:SpringBoot实现ApplicationEvent事件的监听和发布
Java:SpringBoot实现ApplicationEvent事件的监听和发布
106 0
|
设计模式 Java 调度
SpringBoot 事件发布监听机制使用、分析、注意点 (一篇到位)
SpringBoot 事件发布监听机制使用、分析、注意点 (一篇到位)
1369 1
SpringBoot 事件发布监听机制使用、分析、注意点 (一篇到位)
|
Java 数据库
Java-SpringBoot-04-自定义监听器及事件
日常项目中,很多业务场景会用到监听器,比如在Service中处理业务时,我们需要记录一个操作的日志到数据库中,我们怎么实现呢?
348 0
Java-SpringBoot-04-自定义监听器及事件
|
Java Spring
Java-SpringBoot-03-Application事件和监听器
在ApplicationContext创建之前,侦听器怎么注册
104 0
Java-SpringBoot-03-Application事件和监听器