Spring Boot 初始化阶段解析

导语

前面笔者写过一篇关于Spring Boot 自动装配的文章:再说Spring Boot的自动装配原理,这篇文章是从装配的角度上去进行阐述的,其间主要是对@SpringBootApplication 这个注解进行展开,然后进行一些列的相关解析,由此很多童鞋可能并不能看出什么,那么下面笔者将会对此的用意进行说明。

既然说完了自动装配,那么接下来自然要说说Spring Boot的初始化和运行等方面的知识了,其实很多童鞋在使用Spring Boot的时候,并没有对Spring Boot一些东西进行深入的了解,譬如Spring Boot是怎么运行的?它运行之前做了什么?在运行的时候又做了什么?当然本篇文章并不会对这些都进行说明,只是给大家先抛个疑问。那么本篇文章我们要对什么方面进行阐述呢?本篇文章主要是对Spring Boot的初始化阶段进行一个简单的解析说明,这也就是你们所看到的标题了,好了,不多说,我们快点进入主题吧!

一、SpringApplication的构造过程

对这个小标题,有些人可能有些懵,这里的构造是什么意思?当然有这样的疑惑,因为是大家在使用Spring Boot的时候,没有去仔细的去了解Spring Boot的源码,我们先来看段代码(注:本系列文章的Springboot源代码将基于Spring Boot 2.2.5.RELEASE),代码如下:

@SpringBootApplication
public class SpringBootSourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootSourceApplication.class, args);
    }

}

看到这段代码的童鞋可能会有疑问了,这段代码有什么好看的,的确是没什么好看的,可这也是很多人所忽略的。接下来我们就从这里开始了,在这里我们首先看run方法,当然也没什么其他可看的了。不过我还是要提示一下,这里的run方法里面传了两个参数,这也是我们常常会忽略的,第一个参数是当前类,第二个是args,如果当前类没有加上@SpringBootApplication ,程序可以运行起来吗?其结果是肯定运行不起来的,既然把Spring Boot的引导类当做参数,且必须要加上@SpringBootApplication 注解,这是有它的道理的。先不多说,我们看下run方法里面的代码,如下:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

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

上面的第一个方法,便是我们引导类进来的方法,此时你看方法的第一个参数名的命名,它叫:primarySource,字面意思理解是主要资源的意思。在前面的文章中,曾说过@SpringBootApplication 注解具有@EnableAutoConfiguration 作用,因为@SpringBootApplication 注解是一个组合注解,然后当引导类被它标注时,相当于引导类,也就是这里的primarySource,是为Spring Boot应用上下文的Configuration Class。然后引导类进来的run方法里面,又调了一个重命名的run方法,这个run方法是带着primarySource这个参数进去的,在重命名的run方法里面我们看到new了一个SpringApplication类,此时便开始Spring Boot的构建之路了,最后的方法便是SpringApplication的构造函数了。这里便是SpringApplication的主要构造过程了。

这里大体的执行逻辑是:对primarySource进行存储、deduceFromClasspath()、setInitializers(Collection) 、setListeners(Collection),最后是执行deduceMainApplicationClass(),其字面意思就是推断web应用类型、加载Spring应用上下文初始化器、加载Spring应用实践监听器、推断有应用引导类,后面便会以此来作为具体分析方向。

二、推断web应用类型

上面我们对Spring Boot的整个构造过程进行了简单的说明,下面我们就要按照构造过程的步骤继续进行说明,那么接下来的便是推断Web应用类型。推断Web应用类型是属于当前Spring Boot应用Web 初始化的过程,因为此过程在SpringApplication构造之后以及在run方法之前,可通过setWebApplicationType(WebApplicationType)调整。在推断过程中,由于Spring 应用上下文还未准备,所以采用的是检查当前的ClassLoader下基准Class的存在性判断:

public enum WebApplicationType {


	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

}

在这个推断方法中,使用ClassUtils#isPresent(String, ClassLoader)方法来依次判断 DispatcherHandler、DispatcherServlet、ServletContainer、Servlet、ConfigurableWebApplicationContext的组合情况,由此来推断Web的应用类型:

1).当DispatchHandler存在时,并且DispatchServlet不存在时,就是说当Spring Boot只依赖WebFlux存在时,此时的Web应用类型纪委Reactive Web(WebApplicationType.REACTIVE)。

2).当Servlet与ConfigurableWebApplicationContext时,那么当前应用为非Web应用(WebApplicationType.NONE)。

3).当Spring WebFlux和 Spring MVC同时存在时,那么当前的应用为Servelet Web(WebApplicationType.SERVLET)。

三、加载Spring应用上下文初始化器

这个过程分为两步,首先是getSpringFactoriesInstances(Class),然后是调用setInitializers(Collection),前者的调用时委派给getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args),代码如下:

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

此处使用的是Spring的工厂加载机制方法:SpringFactoriesLoader.loadFactoryNames(type, classLoader),按照一开说的在前面讲Spring Boot自动装配的文章里,可以知道这里会返回所有"META-INF/spring.factories"资源配置中的ApplicationContextInitializer的实现类集合,如org.springframework.boot:spring-boot:2.2.5.RELEASE:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

在获取所有的实现类名单后,便调用createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names)方法来对这些实现类进行初始化:

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

在调用重载getSpringFactoriesInstances(type, new Class<?>[] {})方法是,发现这里的第二个参数传的是一个空数组,然后看到在createSpringFactoriesInstances方法中,这个参数被传入instanceClass.getDeclaredConstructor(parameterTypes)方法,由此可以推测ApplicationContextInitializer所有的实现类找那个必须有默认构造器,我们从上面的集合中选一个看看代码:

public class DelegatingApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	private static final String PROPERTY_NAME = "context.initializer.classes";

	private int order = 0;

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			applyInitializerClasses(context, initializerClasses);
		}
	}

	private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
		String classNames = env.getProperty(PROPERTY_NAME);
		List<Class<?>> classes = new ArrayList<>();
		if (StringUtils.hasLength(classNames)) {
			for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
				classes.add(getInitializerClass(className));
			}
		}
		return classes;
	}
    ........此处省略代码........
}

从上面的实现类可以我们看到此实现类还实现了Order接口,由此可知后面将会有排序操作,因为ApplicationContextInitializer的实现类是来自不同的"META-INF/spring.factories"文件资源中的,因此做排序操作也是无可厚非的,但是也不是所有的ApplicationContextInitializer的实现类都需要排序的,也就是不是必须要实现Order接口。在创建完所有的实现类集合后,便开始进行排序操作:AnnotationAwareOrderComparator.sort(instances),在完成排序操作后就是我们的setInitializers操作了,这个操作很简单:其实就是个覆盖性操作。如下:

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
	this.initializers = new ArrayList<>(initializers);
}

四、加载Spring应用实践加载器

此过程与加载Spring应用上下文初始化器的过程基本一致,这里就不赘述了,童鞋们可以自己去看下源码。

五、推断引导类

private Class<?> deduceMainApplicationClass() {
	try {
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

这里是通过当前线程的执行栈来判断其中哪个类包含了“main”方法,虽然此方法不是很严谨,但是足以覆盖大部分以Java标准的main方法作为引导的情况。

至此我们的SpringApplication的初始化阶段便告一段落了。

总结

本文大体的逻辑就是按照构造函数的步骤来进行阐述的:推断web应用类型、加载Spring应用上下文初始化器、加载Spring应用实践监听器、推断有应用引导类。感兴趣的童鞋可以自己阅读源码,感受更细节的代码实现。

 

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/104788743