SpringBoot源码分析之ApplicationListener

本文的关注点是Spring的事件播发机制在SpringBoot中是如何发扬光大的。

1. 概述

在笔者之前的博客 SpringBoot源码研究之Start 中,已经基本完整地阐述了SpringBoot应用地整个启动逻辑,SpringBoot的事件机制正是通过将整个启动逻辑划分为不同的阶段,然后在关键阶段点触发相应的预定义事件,进而回调相应的注册事件处理函数来实现对外扩展。

SpringBoot中对于事件的处理相关的package为 org.springframework.boot.context.event。依然是非常经典的所有相关的类都被封装在一个专门的package中。
Event package

下面就让我们挑选出一些关键性的内容进行分析。

2. 事件监听—— EventPublishingRunListener

关于SpringBoot的启动逻辑,我们在之前的文章中已经分析过一次,因此本次我们将只关注事件相关的。

在以下源码中我们看到一共会触发了五类事件:ApplicationStartingEventApplicationEnvironmentPreparedEventApplicationPreparedEventApplicationReadyEventApplicationFailedEvent(最后这两个互斥,只会触发其中的一个)。这正好对应上图中的 event 子包下的相关类。

// SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	FailureAnalyzers analyzers = null;
	configureHeadlessProperty();
       // 从META-INF/spring.factories文件中检索出SpringApplicationRunListener实现类,默认情况下也就是 EventPublishingRunListener 了。
       //        org.springframework.boot.SpringApplicationRunListener= org.springframework.boot.context.event.EventPublishingRunListener
       // 并将这些检索出来的实现类封装为 SpringApplicationRunListeners,对外提供统一的调用接口, 非常好的思路
	SpringApplicationRunListeners listeners = getRunListeners(args);
       // 触发ApplicationStartingEvent事件
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
           // 触发 ApplicationEnvironmentPreparedEvent 事件
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		analyzers = new FailureAnalyzers(context);
           // 触发 ApplicationPreparedEvent 事件
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
           // 触发 ApplicationReadyEvent / ApplicationFailedEvent 中的某一个事件
		listeners.finished(context, null);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		return context;
	}
	catch (Throwable ex) {
		handleRunFailure(context, listeners, analyzers, ex);
		throw new IllegalStateException(ex);
	}
}

触发顺序正好和笔者之前的博客SpringBoot源码分析之LOG 中所测试的相一致。

另外观察以上源码我们会发现,SpringApplicationRunListeners的构造并不是简单地直接使用new,所以我们需要注意下SpringBoot是如何构建整个SpringApplicationRunListeners实例的,知晓这些扩展点有助于我们在碰到业务需求时候更加从容。

// 我们可以很清晰地看到SpringBoot依然是从META-INF/spring.factories文件中检索出的各个SpringApplicationRunListener实现类,来组装成SpringApplicationRunListeners实例。
private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	// 抽取配置文件中的 SpringApplicationRunListener实现类
	return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
			SpringApplicationRunListener.class, types, this, args));
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<String>(
			SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
			classLoader, args, names);
	// 这里说明了会考虑Ordered接口实现, 或Ordered相关的注解@Order and @Priority。
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

以下就是SpringBoot配置SpringApplicationRunListener,使其可以介入SpringBoot生命周期。
配置SpringApplicationRunListener

最后让我们来看看EventPublishingRunListener本身。通过观察此类的实现我们发现,其对于事件的触发只是简单地迭代调用了所有注册的ApplicationListener。而ApplicationListener的注册方式和上面的SpringApplicationRunListener一致,观察以下截图(其中最后的两个类正是笔者上一篇博客SpringBoot源码分析之LOG 的关键点):
配置ApplicationListener

注意,EventPublishingRunListener使用的是自身构建的ApplicationEventMulticaster实例,而非Spring容器自带的,所以播发的事件不会被Spring容器感知。

以上注册了这么多ApplicationListener,分别完成了SpringBoot在启动和运行过程中各种扩展操作,例如ConfigFileApplicationListener 中就有各种各样的配置参数,例如profile配置文件相关的spring.profiles.active等等。

   // args 为 命令行参数
public EventPublishingRunListener(SpringApplication application, String[] args) {
	this.application = application;
	this.args = args;
	this.initialMulticaster = new SimpleApplicationEventMulticaster();
       // 这里的application.getListeners(), 获取的是从META-INF/spring.factories文件中检索出出来的 ApplicationListener  实现类
	for (ApplicationListener<?> listener : application.getListeners()) {
		this.initialMulticaster.addApplicationListener(listener);
	}
}

3. 事件对象 —— SpringApplicationEvent

与Listener相照应的当然就是事件对象了。虽然Spring 提供了初始版本的事件对象——即继承自EventObjectApplicationEvent。但SpringBoot依然选择了构建了一个抽象层 SpringApplicationEvent,用以保证其自身的稳定性。

Event Object In SpringBoot

上图中我们可以看到,SprnigBoot提供了五种不同类型的事件对象,SpringBoot使用它们来将大部分配置逻辑外置化,而不是硬编码到主体逻辑中,这种微核+扩展的设计方法有着无比强大的生命力。当然使用者也可以通过实现并注册自定义的ApplicationListener来介入到SpringBoot生命周期中。

// 在src/main/resources下新建META-INF/spring.factories文件
// 在上述文件中填入 org.springframework.context.ApplicationListener=com.xx.yy.MyListener
// 启动应用
public final class MyListener implements ApplicationListener<ApplicationEvent> {
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		System.out.println(event.getClass().getName());
	}
}

// ------ 输出顺序
1. org.springframework.boot.context.event.ApplicationStartedEvent
2. org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
3. 输出banner
4. org.springframework.boot.context.event.ApplicationPreparedEvent
5. org.springframework.context.event.ContextRefreshedEvent
6. org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent
7. org.springframework.boot.context.event.ApplicationReadyEvent

4. 总结

  1. SpringBoot增加了SpringApplicationRunListener接口来用于监听SpringBoot应用启动过程中的一些生命周期事件,并做一些处理。
  2. SpringBoot增加了SpringApplicationEvent事件基类和五种实现类,用于针对SpringBoot启动过程中的不同阶段的扩展。并提供了META-INF/spring.factories的配置入口。SpringBoot启动的很多配置信息就是使用这种方式完成的。

5. Links

  1. SpringBoot源码研究之Start
  2. Office Site - Application Events and Listeners
  3. Spring boot源码分析-ApplicationListener应用环境
您可以通过实现 `ApplicationListener` 接口来添加 `SpringBoot` 启动时的监听器。以下是一个示例: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(MyApplication.class); app.addListeners(new MyApplicationListener()); // 添加自定义的监听器 app.run(args); } // 自定义的监听器 public static class MyApplicationListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent event) { // 在应用程序启动时触发的逻辑处理 System.out.println("应用程序启动了!"); } } } ``` 在上面的示例中,我们创建了一个 `MyApplicationListener` 类,实现了 `ApplicationListener` 接口,并重写了其 `onApplicationEvent` 方法,用于在应用程序启动时执行自定义逻辑。 在 `main` 方法中,我们创建了一个 `SpringApplication` 对象,并通过 `addListeners` 方法将自定义的监听器添加到应用程序中,然后调用 `run` 方法启动应用程序。 当应用程序启动时,`onApplicationEvent` 方法将被调用,并执行我们定义的逻辑处理。这里只是简单地打印一条消息,您可以根据实际需求进行相应的处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值