文档章节

SpringBoot源码:启动过程分析(一)

Jacktanger
 Jacktanger
发布于 11/18 00:54
字数 1078
阅读 691
收藏 35

    本文主要分析 SpringBoot 的启动过程。

    SpringBoot的版本为:2.1.0 release,最新版本。

一.时序图

    还是老套路,先把分析过程的时序图摆出来:时序图-SpringBoot2.10启动分析

 

二.源码分析

    首先从我们的一个SpringBoot Demo开始,这里使用 SPRING INITIALIZR 网站生成的starter开始的:

@SpringBootApplication
public class SpringBootDemoApplication {
	public static void main(String[] args) {
        // 分析的入口,从 run 方法开始
		SpringApplication.run(SpringBootDemoApplication.class, args);
	}
}

    经过SpringApplication多个重载的构造方法,最后到达:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 从 run() 传入的 resourceLoader 此处为 null
		this.resourceLoader = resourceLoader;
        // 使用断言判断 resourceLoader 不为空
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 把 primarySources 数组转为List,最后放入 primarySources 的一个LinkedHashSet中
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 判断应用的类型:REACTIVE NONE SERVLET
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 实现 SpringBoot 自动装配的基础,此处Spring自己实现的SPI(从META-INF/spring.factories加载class)
        // 加载并实例化以 ApplicationContextInitializer 为key的类
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
        // 加载并实例化以 ApplicationListener 为key的类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 获取程序当前运行堆栈,看是运行的是哪个类的 main 方法,保存到上下文中
		this.mainApplicationClass = deduceMainApplicationClass();
	}

    看一眼,WebApplicationType#deduceFromClasspath ,deduce意为推断,即根据classpath下的内容推断出应用的类型。实现是通过ClassUtils#isPresent来尝试加载代表不同应用类型特征的Class文件:

    static WebApplicationType deduceFromClasspath() {// 判断应用的类型
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)// 加载到DispatcherHandler
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)// mvc的DispatcherServlet
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {// jersey的ServletContainer
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {// 遍历数组:Servlet和ConfigurableWebApplicationContext
			if (!ClassUtils.isPresent(className, null)) {// 没有加载到Servlet相关的class
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

    SpringApplication#getSpringFactoriesInstances,从类路径下 META-INF/spring.factories 下加载 SpringFactory 实例,类似的操作在 Dubbo SPI中也有:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
        // 获取类加载器
		ClassLoader classLoader = getClassLoader();
        // 此处调用了SpringFactoriesLoader的loadFactoryNames()
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // Use names and ensure unique to protect against duplicates
        // 实例化获取到的类
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);// 排序
		return instances;// 返回实例化好的对象
	}

    SpringFactoriesLoader#loadFactoryNames,加载工厂名字:

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

    继续捉迷藏,到了 SpringFactoriesLoader#loadSpringFactories:下面的内容就是找到所有classpath下的 spring.factories 文件,读取里面的内容,放到缓存中,此处和Dubbo SPI中ExtensionLoader#loadDirectory几乎是一模一样,可以参考我写过的 Dubbo源码 里面的注释。

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // 从缓存中获取Map,key为classLoader
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		try {
            // 加载资源的urls,被加载的资源为 "META-INF/spring.factories"
            //先从Resources中加载,没有加载到再从SystemResources中加载
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {// 遍历加载到的 spring.factories 文件
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                // 读取文件到内存为Properties对象
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    // Entry的key作为工程Class的名字
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        // 如果有多个value,都放在Map中,注意此处为 MultiValueMap ,不是普通的Map,其实现内容的value对应一个LinkedList
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
            // 最后把读取配置的结果都放入缓存中,cache对象为一个ConcurrentReferenceHashMap
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

    我们也来看一下上面读取的文件 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
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
......

    是时候跳出来了,回到主线,返回实例化对象后,到了 SpringApplication#deduceMainApplicationClass,获取程序当前运行堆栈,看现在运行的是哪个类的 main 方法,然后保存到上下文:

    private Class<?> deduceMainApplicationClass() {
		try {
            // 拿到运行时的堆栈信息
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
                // 如果发现哪个堆栈元素里面有运行了main方法,则返回该类
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

 

    至此,SpringApplication的构造函数的分析完成,后面我们继续分析SpringApplication的run()方法中做了哪些操作。

© 著作权归作者所有

共有 人打赏支持
Jacktanger
粉丝 30
博文 112
码字总数 71333
作品 0
浦东
程序员
私信 提问
从SpringBoot源码分析 配置文件的加载原理和优先级

本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数(VM Options)传...

tanliwei
07/13
0
0
springboot情操陶冶-初识springboot

前言:springboot由于其轻便和去配置化等的特性已经被广泛应用,基于时代潮流以及不被鄙视,笔者于是开辟此篇开始认识springboot 前话 springboot是基于spring而开发的轻量级框架,所以在学习...

南柯问天
07/24
0
0
SpringBoot源码:启动过程分析(二)

接着上篇继续分析 SpringBoot 的启动过程。 SpringBoot的版本为:2.1.0 release,最新版本。 一.时序图 一样的,我们先把时序图贴上来,方便理解: 二.源码分析 回顾一下,前面我们分析到了下...

Jacktanger
11/19
0
0
SpringBoot下Redis相关配置是如何被初始化的

参考网页 SpringBoot集成Redis的原理 https://blog.csdn.net/hry2015/article/details/74276423 https://blog.csdn.net/hry2015/article/details/75451705 application.yml配置文件中的属性是......

karma123
07/17
0
0
Spring Boot HelloWorld 解析

开发环境 开发工具:IDEA 2017.2.2 链接: https://pan.baidu.com/s/1qxZLZtBR-Xr7JVkgmhZWzw 密码: wsqp 激活教程:http://idea.lanyus.com/ Spring Boot:2.0.3.RELEASE 参考文档:https://d......

爱编程的帅小伙
07/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Kubernetes 1.13.0的快速升级

Kubernetes 1.13.0已经正式发布,快速升级(含国内镜像快速下载链接)包括升级kubeadm/kubectl/kubelet版本、拉取镜像、升级Kubernetes集群三个主要步骤。注意Kubernetes 1.13.0版本暂时不支...

openthings
18分钟前
2
0
go的卸载和环境变量配个人.bashrc

若是用安装包直接解压 http://download.csdn.net/detail/u010026901/7592581 cd /usr/local tar -zxvf go1.1.2.linux-386.tar.gz(先把安装包移到这个目录) 3.安装 $ cd go/src,$ ./all.b......

dragon_tech
24分钟前
1
0
区块链安全 - 以太坊短地址攻击

1 基础知识 EVM虚拟机在解析合约的字节码时,依赖的是ABI的定义,从而去识别各个字段位于字节码的什么地方。关于ABI,可以阅读这个文档: https://github.com/ethereum/wiki/wiki/Ethereum-C...

HiBlock
34分钟前
1
0
自定义函数及内部函数

变量的作用域 局部变量 global $Global及其他超全局数组 静态变量 仅初始化赋值 保留于内存直到response才销毁 global和static变量的区别 global:局部变量全局话 static:定义静态局部变量 函...

关元
35分钟前
1
0

中国龙-扬科
48分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部