Spring Boot v2.4.4源码解析(三)事件机制篇上

Spring Boot事件发布与订阅机制

一、引入

Spring boot启动方法SpringApplication#run(String...)中有很多关键时间节点:

public ConfigurableApplicationContext run(String... args) {
		// ...
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			// 方法内部会调用listeners.environmentPrepared(bootstrapContext, environment);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			// 方法内部会调用listeners.contextPrepared(context)和listeners.contextLoaded(context);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		// ...
		try {
			listeners.running(context);
		}
		// ...
	}

可以看出整个启动主线包括startingenvironmentPreparedcontextPreparedcontextLoadedstartedrunningfailed时间节点,而对这些时间节点的处理都调用了SpringApplicationRunListeners相应方法。

lass SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

	private final ApplicationStartup applicationStartup;

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,
			ApplicationStartup applicationStartup) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
		this.applicationStartup = applicationStartup;
	}

	void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
		doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
				(step) -> {
					if (mainApplicationClass != null) {
						step.tag("mainApplicationClass", mainApplicationClass.getName());
					}
				});
	}

	// environmentPrepared, contextPrepared, contextLoaded, started, running和starting方法
	// 会调用doWithListeners方法, 这里省略;
	// failed, failedcallFailedListener这里省略, 感兴趣可以参考源码;

	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
		doWithListeners(stepName, listenerAction, null);
	}

	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		StartupStep step = this.applicationStartup.start(stepName);
		// 依次调用listeners中每个SpringApplicationRunListener相应方法
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
			stepAction.accept(step);
		}
		step.end();
	}

二、调用链

SpringApplicationRunListeners 对这些时间节点的处理只是依次调用了listeners中每个SpringApplicationRunListener相应处理这些时间节点的方法,并在处理这些时间节点前后调用DefaultApplicationStartup#startStartupStep#end启动和结束相应时间阶段,那listeners中包含哪些SpringApplicationRunListener呢?
看下SpringApplicationRunListeners工厂方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger,
			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
			this.applicationStartup);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

getRunListeners最终会调用了一个很重要方法SpringFactoriesLoader#loadFactoryNames获取SpringApplicationRunListener接口实现类名,那这些类名来自哪里呢?

1. Spring Boot SPI 机制

如果熟悉java SPI,可以清晰的类比SpringFactoriesLoaderSpringjava SPI的一种私有扩展,该方法执行步骤如下:

  • 首先尝试从内存缓存中获取,如获取到就立即返回,没有则继续下面步骤;
  • 扫描 classpath 中的 META-INF/spring.factories文件;
  • 循环遍历读取这些文件中的键值对(K->List<V>);
  • 将读取内容放入内存缓存,下次再调用这个方法时会优先从缓存中获取;
  • 上一步返回的是Map<String, List<String>>,还需要以全类名type作为key,从Map中获取对应值;

org/springframework/boot/spring-boot/2.4.4/spring-boot-2.4.4-sources.jar Jar包spring.factories文件中有如下几行:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

说明

Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

这行代码运行结果是names肯定包含org.springframework.boot.context.event.EventPublishingRunListener这一项。而且其他Jar包的spring.factories文件中均不包含org.springframework.boot.SpringApplicationRunListener,所以names只包含这一个元素。

createSpringFactoriesInstances()方法,顾名思义是对names集合中所有全类名实例化,其中几行关键代码:

for (String name : names) {
	try{
		// 根据全类名获取Class
		Class<?> instanceClass = ClassUtils.forName(name, classLoader);
		Assert.isAssignable(type, instanceClass);
		// 获取参数类型为new Class<?>[] { SpringApplication.class, String[].class }的构造函数
		Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
		// 反射创建对象, 传参为{this, args}, this为SpringApplication本身,args为启动函数传参
		T instance = (T) BeanUtils.instantiateClass(constructor, args);
		instances.add(instance);
	}
	catch (Throwable ex) {
		throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
	}
}

再看一下EventPublishingRunListener参数为new Class<?>[] { SpringApplication.class, String[].class }的构造函数,其实其只有这个构造函数。

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();
	// 将SpringApplication中的所有listener添加到initialMulticaster中
	for (ApplicationListener<?> listener : application.getListeners()) {
		this.initialMulticaster.addApplicationListener(listener);
	}
}

SpringApplication中的listeners又是什么呢?

2、Spring Boot 事件

到这里才正真出现事件概念,看下SpringApplication对listeners`的定义:

private List<ApplicationListener<?>> listeners;

在这里插入图片描述
listeners泛型为ApplicationListenerApplicationListener才是正真是事件监听者,ApplicationListener接口只继承JDK的事件监听者EventListener,说明Spring时间可以和JDK事件通用:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	// 事件event发生时的处理逻辑
	void onApplicationEvent(E event);

	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}
}

在这里插入图片描述

泛型E表示监听的事件类型,需要继承ApplicationEventApplicationEvent表示具体事件类型,自定义事件必须继承该接口,由于Spring兼容JDK事件,所以ApplicationEvent继承JDK事件EventObject
SpringApplication会在构造函数中完成对listeners的初始化操作:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 初始化listeners
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

可以看出Spring Boot 也是使用SPI机制对listeners进行初始化的。

3. 调用链路

现在回头看一下,Spring Boot启动过程中每个时间节点(startingenvironmentPreparedcontextPreparedcontextLoadedstartedrunningfailed)会调用SpringApplicationRunListeners相应方法,而SpringApplicationRunListeners会调用内部listeners中每个SpringApplicationRunListener相应处理这些时间节点的方法,而SpringApplicationRunListeners内部的listeners只包含一个EventPublishingRunListener,相当于调用EventPublishingRunListener相应方法;

SpringApplication -> SpringApplicationRunListeners -> EventPublishingRunListener

看下EventPublishingRunListener对这些时间节点作何处理:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	// 构造方法上文已贴出, 这里省略
	// 省略 getOrder()

	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		// 广播事件
		this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}
	
	// environmentPrepared, contextPrepared函数类似starting函数, 这里省略
	
	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		// 如果时间监听器实现ApplicationContextAware接口, 则调用setApplicationContext()方法回写SpringApplication
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		// 广播事件
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

	// running方法类似started方法, 这里省略

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

	// 省略内部静态类LoggingErrorHandler 
}

从源码可以看出,EventPublishingRunListener对所有时间节点都做了一件时间:将时间节点封装成事件,然后通过SimpleApplicationEventMulticaster或者ConfigurableApplicationContext广播出去。
时序图如下:
在这里插入图片描述

图中ConfigurableApplicationContext实现ApplicationEventPublisher接口。

在前文已经分析过,EventPublishingRunListener构造函数会将所以ApplicationListener事件监听器设置到SimpleApplicationEventMulticaster中,这样SimpleApplicationEventMulticaster在广播事件时,就知道哪些监听器监听了哪些时间节点事件,如果监听了该事件,就调用该监听器的处理逻辑。
所以如果组件对Spring boot启动过程中某个时间感兴趣,只需要实现ApplicationListener接口,并将实现类加入到META-INF/spring.factories文件即可,这样就可以实现启动过程和组件自定义逻辑代码解耦合。
org/springframework/boot/spring-boot/2.4.4/spring-boot-2.4.4-sources.jar Jar包的META-INF/spring.factories文件包含如下几行:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

这里添加了几个事件监听器,简单看下EnvironmentPostProcessorApplicationListener在Spring Boot环境准备好后做的逻辑处理:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	ConfigurableEnvironment environment = event.getEnvironment();
	SpringApplication application = event.getSpringApplication();
	for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
		postProcessor.postProcessEnvironment(environment, application);
	}
}

EnvironmentPostProcessorApplicationListener会在Spring Boot环境准备好后调用所有EnvironmentPostProcessors后置处理方法,在后置方法中可以做添加属性源等操作。
比如其中一个环境后置处理器RandomValuePropertySourceEnvironmentPostProcessor后置处理逻辑是,在环境准备好后添加随机数属性源RandomValuePropertySourceEnvironment就可以解析随机数占位符。

@Component
public class FooBar implements EnvironmentAware, CommandLineRunner{
    
    private Environment env;

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }

    @Override
    public void run(String... args) throws Exception {

        // 随机生成一个整数
        System.err.println(env.resolvePlaceholders("${random.int}"));
        // 随机生成一个整数,指定上边界,不大于等于1
        System.err.println(env.resolvePlaceholders("${random.int(1)}"));
        //  随机生成一个整数,使用区间[0,1)
        System.err.println(env.getProperty("random.int(0,1)"));
        // 随机生成一个整数,使用区间[100,101),前闭后开=>只能是100
        System.err.println(env.getProperty("random.long(100,101)"));
        // 随机生成一个 uuid
        System.err.println(env.resolvePlaceholders("${random.uuid}"));
    }

}

SimpleApplicationEventMulticaster不仅是这里用到,在Spring Boot中的自定义事件相关逻辑也是由它实现,所以该类异常重要。由于篇幅原因,这部分内容下篇博客解析。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值