欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot构造流程浅析

程序员文章站 2022-07-05 21:21:49
什么是Spring Boot的构造流程?即run方法的初始化类SpringApplication的实例化过程。我们都知道Spring Boot项目的启动非常简单,只需要运行入口类的main方法即可,如下:@SpringBootApplicationpublic class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.cl....

什么是Spring Boot的构造流程?即初始化类SpringApplication的实例化过程。

我们都知道Spring Boot项目的启动非常简单,只需要运行入口类的main方法即可,如下:

@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class);
    }
}

可以看到main方法中只有一句代码:SpringApplication.run(xxxx.class),我们进入这个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);
	}

仔细看这句代码:new SpringApplication(primarySources).run(args),发现居然new了一个SpringApplication去调用另外一个run方法,其实这句代码包含了两个非常重要的内容,即Spring Boot的构造流程和运行流程,构造流程是指SpringApplication类的实例化过程,运行流程是指SpringApplication类的实例化对象调用run方法完成整个项目的初始化和启动的过程,而本文的重点是前者。

到这一步,我们基本能够明白一件事:入口类中主要通过SpringApplication的run方法进行SpringApplication类的实例化操作,然后这个实例化对象再去调用另外一个更牛逼的run方法来完成整个项目的初始化和启动。

下面我们将正式进入SpringApplication类的实例化过程的探析,首先看一下SpringApplication两个构造方法的源码:

	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		//赋值成员变量resourceLoader
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		//赋值成员变量primarySources 
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//推断Web应用类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//加载并初始化ApplicationContextInitializer及相关实现类
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		//加载并初始化ApplicationListener及相关实现类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//推断main方法
		this.mainApplicationClass = deduceMainApplicationClass();
	}

第一个构造方法其实是直接调用了第二个核心构造方法,核心业务逻辑便在其中,下面将详细讲解核心构造方法涉及到的业务逻辑。

一、赋值成员变量:resourceLoader、primarySources

可以看到核心构造方法包含两个参数:ResourceLoader和Class<?>...primarySources。其中前者为资源加载的接口,在Spring Boot启动时可以通过它来指定需要加载的文件路径;后者默认传入的是Spring Boot入口类,作为项目的引导类。构造方法的第一个步骤非常简单,就是将传进来的这两个参数赋值给对应的成员变量。

二、推断Web应用类型

 接着是调用了WebApplicationType的deduceFromClasspath方法来推断Web应用类型,我们首先进入WebApplicationType类:

public enum WebApplicationType {
	//非Web应用类型
	NONE,
	//基于Servlet的Web应用类型
	SERVLET,
	//基于Reactive的Web应用类型
	REACTIVE;
        ...
}

可以知道WebApplicationType类只是一个枚举类型,包括:非Web应用类型,基于Servlet的Web应用类型,基于Reactive的Web应用类型。另外可以在WebApplicationType类中看到刚才提到的推断方法deduceFromClasspath(),推断方法以及用于推断的常量源码如下。

	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() {
                //如果类路径中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,则为Reactive应用
		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) {
                        //如果类路径下Servlet或者ConfigurableWebApplicationContext任何一个不存在,则为非Web应用
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
                //否则都是Servlet应用类型
		return WebApplicationType.SERVLET;
	}

isPresent方法可以通过反射机制创建出指定类,根据在创建过程中是否抛出异常来判断指定类是否存在。分析该推断方法可以得知核心逻辑是通过ClassUtils.isPresent()来判断类路径classpath下是否存在指定类,从而判断出应用类型。推断逻辑如下:

  1. 如果类路径中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,则为Reactive应用。
  2. 如果类路径下Servlet或者ConfigurableWebApplicationContext任何一个不存在,则为非Web应用。
  3. 其他情况则为Servlet应用类型。

三、加载并初始化ApplicationContextInitializer及相关实现类

ApplicationContextInitializer的作用:它是Spring IOC容器提供的一个回调接口,通常用于应用程序上下文进行编程初始化的Web应用程序中。

在完成Web应用类型推断之后,接着便开始ApplicationContextInitializer的加载工作,这里将分成两个步骤:即获取相关实例和设置实例。对应的方法为:getSpringFactoriesInstances()和setInitializers()。我们首先来看getSpringFactoriesInstances()方法,源码如下:

	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 = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
                //加载META-INF/spring.factories文件中的对应配置
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
                //创建实例
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
                //排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

可以看到这里是通过SpringFactoriesLoader.loadFactoryNames()方法来加载META-INF/spring.factories文件中的对应配置,该文件的内容如下:

Spring Boot构造流程浅析

 看到这里大家可能会觉得似曾相识,没错,这里加载META-INF/spring.factories文件的过程在之前讲解Spring Boot自动配置时已经提到过,spring.factories文件中的内容会被解析到Map<String,List<String>>中,最后loadFactoryNames通过传递过来的class名称作为Key从Map中获得该类的配置列表,而这个class名称就是type的值,追溯type的值发现其实就是一开始传入的ApplicationContextInitializer.class。

上面通过SpringFactoriesLoader.loadFactoryNames()方法获取到了ApplicationContextInitializer接口具体的实现类的全限定名,下面就要调用createSpringFactoriesInstances()方法来创建这些实例,再将这些实例经过排序后返回,至此获取相关实例结束,下一步是设置实例。

下面看设置实例的方法:setInitializers()

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

可以看到设置实例的步骤很简单,将SpringApplication的initializers成员变量实例化为一个新的List,然后将刚才获取到的实例放入其中即可。至此加载并初始化ApplicationContextInitializer及相关实现类结束。

四、加载并初始化ApplicationListener及相关实现类

ApplicationListener经常用于监听容器初始化完成之后,执行数据加载、初始化缓存等任务。

ApplicationListener的整个加载流程与ApplicationContextInitializer的加载流程完全相同,这里就不再重复。

五、推断main方法

最后一步是通过deduceMainApplicationClass()推断main方法,来看源码:

	private Class<?> deduceMainApplicationClass() {
		try {
                        //通过新建一个运行时异常来获得栈数组
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
                        //遍历栈数组
			for (StackTraceElement stackTraceElement : stackTrace) {
                                //匹配出第一个main方法
				if ("main".equals(stackTraceElement.getMethodName())) {
                                        //返回该类的class对象
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

该方法首先通过一个运行时异常来获得栈数组,接着遍历这个数组寻找出第一个“main”方法,如果找到了这个main方法,就通过返回这个类的对象,并最终将这个对象赋值给SpringApplication的成员变量mainApplicationClass。至此,SpringApplication的实例化过程结束!

现将SpringApplication类的实例化过程涉及到的核心操作总结如下:

  1. 赋值成员变量:resourceLoader、primarySources
  2. 推断Web应用类型
  3. 加载并初始化ApplicationContextInitializer及相关实现类
  4. 加载并初始化ApplicationListener及相关实现类
  5. 推断main方法

 

 

本文地址:https://blog.csdn.net/h2503652646/article/details/109609182

相关标签: Spring Boot