Loading

SpringBoot启动流程

前言

为什么要学习框架的启动流程?
因为这不仅是面试中常见的考点,而且掌握一个框架的启动流程,可以让你在运用它进行开发时游刃有余。甚至可以说,学习一个框架的起步,都是从学习框架的启动开始的。

下面会从源码角度对启动流程做一个剖析,建议读者可以跟着文章内容进行调试,那样可以理解的更透彻。

源码版本为 spring-boot-2.2.1.RELEASE.jar

启动流程

初始化SpringApplication对象

  • 获取所有的ApplicationContextInitializer接口的实现类
  • 获取所有的ApplicationListener接口的实现类
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();
        // 获取所有的ApplicationContextInitializer接口的实现类
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 获取所有的ApplicationListener接口的实现类
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

如何获取到实现类的呢?主要通过 getSpringFactoriesInstances 方法

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);
        // Spring内置排序器对实例进行排序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

那 SpringFactoriesLoader 的 loadFactoryNames 方法如何工作

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
        // 返回 factoryType 下的实现类名列表
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

// 获取类加载器下 META-INF/spring.factories 资源并解析
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
        // 如果缓存中存在则直接返回
	if (result != null) {
		return result;
	}

        // FACTORIES_RESOURCE_LOCATION="META-INF/spring.factories"
        // 只要外部依赖中存在 spring.factories 文件配置,那么就会将文件绝对路径加载到 urls
	Enumeration<URL> urls = (classLoader != null ?
						classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
						ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
	result = new LinkedMultiValueMap<>();
        // 解析每个 spring.factories 文件
	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;
	}
}

spring.factories 内容示例:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

SpringApplication对象调用run方法

(1)构造 SpringApplicationRunListeners,用来监听启动过程中的事件并回调。
SpringApplicationRunListeners 会存储 SpringApplicationRunListener 的实现类实例,并构成链表结构,默认只有一个 EventPublishRunListener,且优先级最高,位于链表首部。

获取所有 Listener 还是通过 getSpringFactoriesInstances 方法

private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger,
					getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListener 调用 starting 方法

class SpringApplicationRunListeners {
	private final List<SpringApplicationRunListener> listeners;

	void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}
}

SpringApplicationRunListener 回调匹配类型的 ApplicationListener 的 onApplicationEvent 方法(使用见 spring自定义listener#自定义ApplicationListener

@Override
public void starting() {
        // 广播事件
	this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

@Override
public void multicastEvent(ApplicationEvent event) {
	multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
        // getApplicationListeners 获取匹配 type 类型的听众
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		} else {
			invokeListener(listener, event);
		}
	}
}

(2)封装命令行参数,创建 Environment,回调 environmentPrepared 事件,并打印横幅(banner)

Environment 就是 Spring 环境的抽象,包括 profile 和 properties 两个方面。Environment 对于 profile 来说,可以在不同环境下注册不同的 bean;对于 properties 来说,提供解析和读取属性配置的工具。

// 封装参数到 ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// listeners 回调 environmentPrepared 事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 打印横幅
Banner printedBanner = printBanner(environment);

(3)加载上下文
实例化 ApplicationContext,需要判断创建 service web context, 还是新出现的 reactive web context, 或者默认的 context

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

预处理上下文

// 将环境变量写入上下文
context.setEnvironment(environment);
// 回调 ApplicationContextInitializer 的 initialize 方法
applyInitializers(context);
// 回调 Listener 的 contextPrepared 和 contextLoaded
listeners.contextPrepared(context);
listeners.contextLoaded(context);

刷新上下文

  • 完成 ioc 容器构建,如果是 web 应用会创建内嵌的 Tomcat
  • 扫描,加载外部组件自动配置信息(自动配置 @EnableAutoConfiguration)
  • 回调 Listener 的 started
  • 从ioc容器中获取所有的 ApplicationRunner 和 CommandLineRunner 进行回调。ApplicationRunner 先回调,CommandLineRunner 再回调

AutoConfigurationImportSelector#getAutoConfigurationEntry 加载自动配置信息

AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 过滤掉条件不满足的自动配置类(ConditionalOn)
configurations = filter(configurations, autoConfigurationMetadata);

总结:
spring 调用 run 方法,其实就是在调用 Event 来触发 SpringApplicationRunListener 接口的生命周期函数,通过回调相应函数来完成 spring 前置配置、bean初始化、tomcat 初始化等等。

spring 每个 event 的执行动作见 spring event执行动作

列举比较重要的 event:

ApplicationContextInitializedEvent,context 初始化完成,但还没有初始化 bean;
ApplicationPreparedEvent,context 刷新之前,加载 BeanDefinition,依然还没有初始化 bean;
ApplicationStartedEvent,在 context 刷新之后执行,此时 spring 容器已经初始化完成,并且 bean 已经正确实例化;
ApplicationReadyEvent,spring 容器初始化完成后,接下来就是加载中间件,比如 Tomcat,Tomcat 启动完成后回调该事件。

posted @ 2021-08-12 17:00  flowers-bloom  阅读(180)  评论(0编辑  收藏  举报