Spring Boot构造流程浅析

第一篇:Spring Boot自动配置原理浅析

第二篇:Spring Boot构造流程浅析

第三篇:Spring Boot运行流程浅析

目录

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

二、推断Web应用类型

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

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

五、推断main方法


我们都知道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文件中的对应配置,该文件的内容如下:

 看到这里大家可能会觉得似曾相识,没错,这里加载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方法

第一篇:Spring Boot自动配置原理浅析

第二篇:Spring Boot构造流程浅析

第三篇:Spring Boot运行流程浅析

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大何向东流1997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值