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

栏目: Java · 发布时间: 6年前

内容简介:本文主要分析 SpringBoot 的启动过程。SpringBoot的版本为:2.1.0 release,最新版本。还是老套路,先把分析过程的时序图摆出来:时序图-SpringBoot2.10启动分析

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

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

一.时序图

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

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

二.源码分析

首先从我们的一个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()方法中做了哪些操作。

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机网络(第5版)

计算机网络(第5版)

Andrew S. Tanenbaum、David J. Wetherall / 严伟、潘爱民 / 清华大学出版社 / 2012-3-1 / 89.50元

本书是国内外使用最广泛、最权威的计算机网络经典教材。全书按照网络协议模型自下而上(物理层、数据链路层、介质访问控制层、网络层、传输层和应用层)有系统地介绍了计算机网络的基本原理,并结合Internet给出了大量的协议实例。在讲述网络各层次内容的同时,还与时俱进地引入了最新的网络技术,包括无线网络、3G蜂窝网络、RFID与传感器网络、内容分发与P2P网络、流媒体传输与IP语音,以及延迟容忍网络等。另......一起来看看 《计算机网络(第5版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码