SpringBoot启动流程中的Initializer和Listeners

SpringBoot启动大致需要分为两个阶段,第一个阶段就是实例化一个SpringApplication对象,第二个阶段就是调用这个对象的run()方法。
在SpringApplication的构造方法中,分别设置了Listeners和Initializers。

1.Initializers

1.定义

private List<ApplicationContextInitializer<?>> initializers;

在SpringApplication中定义了一个泛型为ApplicationContextInitializer<?>的List集合。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    
    

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

ApplicationContextInitializer的泛型是ConfigurableApplicationContext或者其子类,在类中有一个void initialize(C applicationContext)方法,参数是ConfigurableApplicationContext或者其子类。根据SpringBoot的启动流程我们可以知道initializers是在prepareContext()中的applyInitializers(context)使用的。
在这里插入图片描述

已知prepareContext()发生在refreshContext()之前,也就是刷新容器之前。那么ApplicationContextInitializer的作用就是在ConfigurableApplicationContext类型或者其子类型的ApplicationContext做refresh之前,允许我们对ConfigurableApplicationContext做进一步的设置和处理。
数量为7个
在这里插入图片描述
对应文件
在这里插入图片描述
在这里插入图片描述
大致作用:
1.DelegatingApplicationContextInitializer: 委派处理ApplicationContext初始化器,其需要委派处理的初始化器来自Spring环境中的context.initializer.classes属性,该属性可以使用逗号分隔多个初始化器。
2.ContextIdApplicationContextInitializer:为ApplicationContext设置id。根据以下的配置顺序来设置,spring.application.name、vcap.application.name、spring.config.name,如果环境配置中都没有这些配置,则默认使用“application”来表示,另外还会将profiles也加入到id中去。
3.ConfigurationWarningsApplicationContextInitializer:输出警告日志信息。
4.ServerPortInfoApplicationContextInitializer:添加一个EmbeddedServletContainerInitializedEvent事件监听,触发设置嵌入的WEB服务启动端口。通过属性local.[namespace].port来设置启动端口,其中namespace为ApplicationContext指定的命名空间,如果命名空间为空,则使用local.server.port属性来表示配置的端口。
5.SharedMetadataReaderFactoryContextInitializer:和Spring Boot共享CachingMetadataReaderFactory。
6.RSocketPortInfoApplicationContextInitializer:ApplicationContextInitializer 为 RSocketServer 服务器实际侦听的端口设置环境属性。属性“local.rsocket.server.port”可以使用@Value 直接注入测试或通过环境获取。属性会自动传播到任何父上下文。
7.ConditionEvaluationReportLoggingListener:将 ConditionEvaluationReport 写入日志的 ApplicationContextInitializer。报告记录在 DEBUG 级别。崩溃报告会触发信息输出,建议用户在启用调试的情况下再次运行以显示报告。此初始化程序不打算在多个应用程序上下文实例之间共享。

2.获取

在SpringApplication的构造器中有一个setInitializers方法,它的作用就是获取一个initializer列表,然后放到SpringApplication中。

	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();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这两个方法是SpringApplication中的私有方法,作用就是根据给定的类型,返回相应的实例列表。
getSpringFactoriesInstances方法:这里传入的是ApplicationContextInitializer类

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    
    
		return getSpringFactoriesInstances(type, new Class<?>[] {
    
    });
	}

	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;
	}

这里主要有4个方法

1.getClassLoader()

获取当前设置好的ClassLoader若,resourceLoader == null,则返回默认的classLoader也就是AppClassLoader。

	public ClassLoader getClassLoader() {
    
    
		if (this.resourceLoader != null) {
    
    
			return this.resourceLoader.getClassLoader();
		}
		return ClassUtils.getDefaultClassLoader();
	}

2.SpringFactoriesLoader.loadFactoryNames

根据type和classLoader调用SpringFactoriesLoader.loadFactoryNames方法,获取当前项目下所有的需要加载的类的全限定名。
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
loadFactoryNames会调用loadSpringFactories方法
这个方法是我们获取资源文件的关键,首先会从cache中查询是否加载过资源文件,如果有就直接返回,没有的话就去读文件。

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    
    
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    
    
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
    
    
			return result;
		}

		try {
    
    
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
    
    
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
    
    
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
    
    
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
    
    
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

主要看try中的方法

			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

文件路径
在这里插入图片描述
这个方法就是用来读取资源文件的,classLoader.getResources()会查询当前项目和所有依赖的jar包。那么这句话就可以解释为读取当前项目下和所有依赖的jar包,获取到所有META-INF/spring.factorie的文件路径。

			while (urls.hasMoreElements()) {
    
    
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
    
    
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
    
    
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);

遍历获取到的urls,根据url读取资源,将所有spring.factories文件中的资源加载出来然后构建成一个key-value形式的map。
接下来的工作就是将result放到cache中,然后返回。
当loadSpringFactories执行完成返回结果之后,会调用getOrDefault方法。
Map.getOrDefault(Object,V):这个方法是map为我们提供的一个方法,作用就是若map中有这个key时就用这个key的值,如果没有就使用默认值。

3.通过反射创建所获得的全类名对象。

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
    
    
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
    
    
			try {
    
    
				//获取到Class对象
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				//获取构造去
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				//创建对象
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
    
    
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

4.对实例化的对象进行排序。

3.使用

在我们说initializers的用法的时候,说到它的作用就是在refresh上下文之前允许我们对context做一下操作,也就是一个回调函数。

prepareContext(context, environment, listeners, applicationArguments,printedBanner)

在refreshContext之前调用,进行上下文的准备工作

applyInitializers(context);

遍历Initializers集合,调用initializer对象的initializer方法。

	protected void applyInitializers(ConfigurableApplicationContext context) {
    
    
		for (ApplicationContextInitializer initializer : getInitializers()) {
    
    
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

2.listeners

1.定义

private List<ApplicationListener<?>> listeners;

我们可以看到在SpringApplication中定义了一个泛型为ApplicationListener<?>的List集合。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    
    

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

ApplicationListener是继承自EventListener的,所有的事件监听器都需要继承EventListener作为一个标记接口。

ApplicationListener的泛型是ApplicationEvent的子类型,其中定义了onApplicationEvent方法,需要我们传入一个Event对象。

在Spring中,事件(ApplicationEvent)为bean和bean之间的消息通信提供了支持。其实ApplicationContext事件机制是观察者模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext的事件机制。

ApplicationEvent代表事件,ApplicationListener代表监听器需要传入特定事件,当程序做完某种动作的时候,会发布事件,此时正在监听这个事件的监听器的onApplicationEvent方法就会调用,从而进行特有的逻辑处理。

2.获取

ApplicationListener的获取方式和ApplicationInitializer的获取方法流程都是一样的,不过有一点就是,我们在SpringFactoiesLoader.loadSpringFactories方法时,会将从文件中读取到的数据存放到内存中,所以当我们在获取系统默认的ApplicationListener的时候,是不会进行IO操作读取文件的,而是直接从cache中获取,根据传入的key获取相应的列表,然后实例化。
10个
在这里插入图片描述

9个
在这里插入图片描述

1个
在这里插入图片描述

3.使用

另一个监听器:SpringApplicationRunListener
从名字上我们就可以知道这个类的作用就是SpringBoot启动过程中的监听器。
这个监听器会调用构造函数中引入的那10个监听器。
在这里插入图片描述
获取方式和ApplicationListener一样。
在这里插入图片描述
在这里插入图片描述

**EventPublishingRunListener类:**
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    
    

	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();
		//获取所有的ApplicationListener
		for (ApplicationListener<?> listener : application.getListeners()) {
    
    
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
    
    
		return 0;
	}

	@Override
	public void starting() {
    
    
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
    
    
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
    
    
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
    
    
		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));
	}

	@Override
	public void running(ConfigurableApplicationContext context) {
    
    
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
	}

	@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);
		}
	}

	private static class LoggingErrorHandler implements ErrorHandler {
    
    

		private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);

		@Override
		public void handleError(Throwable throwable) {
    
    
			logger.warn("Error calling ApplicationEventListener", throwable);
		}

	}

}

在构造方法中,将SpringApplication中的listeners全部注册到当前的注册表中.因为按照Spring的事件机制,事件的注册时机发生在refresh中,如果我们想监听SpringBoot的启动过程的话,则需要先将我们加载的监听器注册到注册表中。这样就和Spring的注册表在职能上没有关系了,这里面的注册表是EventPublishingRunListener的一个内部属性。

接下来就是实现SpringApplicationRunListener的七个方法了,它们会调用EventPublishingRunListener的内部属性initialMulticaster的广播方法,将相对应的事件对象广播出去,因为在构造方法中已经注册了监听器,那么这些事件对象就可以被相应的事件监听从而处理。

在run方法中有几处用到这个监听器
1.listeners.starting();的时候
2.prepareEnvironment的时候
3.prepareContext的时候
4.listeners.contextLoaded(context); 在prepareContext最后
5.publishEvent(new ContextRefreshedEvent(this)); 在refresh finishRefresh中
6.publishEvent(new ServletWebServerInitializedEvent(webServer, this));在refresh中finishRefresh会调用子类(ServletWebServerApplicationContext)的finishRefresh(方法)
7.listeners.started(context)
8.handleRunFailure的时候
9.listeners.running(context)

	public ConfigurableApplicationContext run(String... args) {
    
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//1
		listeners.starting();
		try {
    
    
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//2
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] {
    
     ConfigurableApplicationContext.class }, context);
			//3		//4
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
    
    
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//7
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
    
    
			//8
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
    
    
			//9
			listeners.running(context);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
	protected void finishRefresh() {
    
    
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event. //5
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}
	protected void finishRefresh() {
    
    
		super.finishRefresh();
		WebServer webServer = startWebServer();
		if (webServer != null) {
    
    
			//6
			publishEvent(new ServletWebServerInitializedEvent(webServer, this));
		}
	}

第一处的事件 listeners.starting();
在这里插入图片描述
在这里插入图片描述

第二处的事件 prepareEnvironment
在这里插入图片描述

在这里插入图片描述

第三处的事件 prepareContext
在这里插入图片描述
在这里插入图片描述
第四处事件 listeners.contextLoaded(context);
在这里插入图片描述
在这里插入图片描述

第五处事件
在这里插入图片描述
在这里插入图片描述

第六处事件
在这里插入图片描述

在这里插入图片描述

第七处事件 listeners.started(context);

在这里插入图片描述
1.
第八处是错误 handleRunFailure(context, ex, exceptionReporters, listeners);
第九处事件 listeners.running(context);
在这里插入图片描述
在这里插入图片描述

这些监听器的作用,还需要继续研究啊~~~~
1.AnsiOutputApplicationListener
用来设置ANSI的彩色输出,一般是让集成开发工具输出彩色日志,使得信息更具可读性。
2.ClasspathLoggingApplicationListener
该监听器在springboot应用程序刚调用SpringApplication#run方法时,在执行具体的准备环境,刷新容器等操作前,通过springboot自定义的SpringApplicationRunListener的org.springframework.boot.SpringApplicationRunListener#starting来完成调用。完成打印应用程序的classpath信息的工作。
3.BackgroundPreinitializer
该监听器实现了ApplicationListener接口,用于在应用程序启动是提前执行一些耗时的初始化操作
4.DelegatingApplicationListener
该类的功能类似于DelegatingApplicationContextInitializer,DelagatingApplicationContextInitializer是用来读取通过环境变量context.initializer.classes配置的ApplicationContextInitializer,DelegatingApplicationListener是用来读取通过环境变量context.listener.classes配置的ApplicationListener。
5.ClearCachesApplicationListener
该监听器用来在容器完成刷新后,即在finishRefresh方法中,清空相关的缓存,对应的事件是ContextRefreshedEvent
6.FileEncodingApplicationListener
该类是检测属性配置file.encoding是否和spring.mandatory-file-encoding一致(如果有该属性的话),如果是不一致,则抛出异常
7.LiquibaseServiceLocatorApplicationListener
使用一个可以和 SpringBoot 可执行jar包配合工作的版本替换 liquibase ServiceLocator
8.LoggerApplicationListener
配置日志系统。使用logging.config环境变量指定的配置或者缺省配置
9.ConfigFileApplicationListener:EnvironmentPostProcessor
从常见的那些约定的位置读取配置文件,比如从以下目录读取application.properties,application.yml等配置文件:
classpath:
file:.
classpath:config
file:./config/
10.ParentContextAvailableEvent/ContextClosedEvent
监听双亲应用上下文的关闭事件并往自己的孩子应用上下文中传播,相关事件

猜你喜欢

转载自blog.csdn.net/weixin_46666822/article/details/124619117