我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Springboot启动流程详细分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Springboot启动流程详细分析

springboot启动是通过一个main方法启动的,代码如下

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

从该方法我们一路跟进去,进入SpringApplication的构造函数,我们可以看到如下代码primarySources,为我们从run方法塞进来的主类,而resourceLoader此处为null,是通过构造函数重载进来,意味着这里还有其它方式的用法,先绕过。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//此处推断web容器类型是servlet 还是REACTIVE还是啥也不是即当前非web容器
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//此处进入springFacories的加载 即SpringFactoriesLoader,不过此处是过滤处所有ApplicationContextInitializer的子类
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		//从springFactories的缓存中再过滤处	ApplicationListener 的子类	
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

所以我们来看SpringFactoriesLoader中的loadSpringFactories方法,先获取资源路径META-INF/spring.factories

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		try {
			//classLoader 上一步传进来的为AppClassLoader,如果对classLoader不了解需要去看看java的类加载机制,以及双亲委托机制,此处的资源加载也是个双亲委托机制。
			//此处如果appclassLoader不为null,那么遍历遍历这个classLoader所加载的包,中是否存在META-INF/spring.factories这个文件
			//如果存在最终生成一个集合,然后遍历集合
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				//从集合中取出一个spring.factories文件
				UrlResource resource = new UrlResource(url);
				//读取对应文件内容,作为一个properties对象
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				//解析文件内容,并将之放入一个LinkedMultiValueMap中,key就是spring.factories中等号前面的部分,值是后面部分根据都好组成的list。并放入缓存备用,缓存的key为对应的classLoader
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

以上代码执行完成后形成的内容大概是这样的,key表示factoryClassName

result = new LinkedMultiValueMap<>();
list=new LinkList();
list.add("com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration");
list.add("org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration")
result.put("org.springframework.boot.autoconfigure.EnableAutoConfiguration",list);

所以此时回退到这个方法setInitializers((Collection)

getSpringFactoriesInstances(ApplicationContextInitializer.class));

就是从遍历出来的所有的这些内容中获取出key为org.springframework.context.ApplicationContextInitializer 的集合

设置该当前类的initializers集合,后面的Listeners 集合也是一样的过程。只不过,后一次获取是从上一次的缓存中取的。

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

设置完initializer和listener集合后,进行主函数推断

接着看代码,此处对主函数的推断非常的巧妙,就是利用虚拟机运行时已被入栈的所有链路一路追踪到方法名为main的函数,然后获取到他的类名。

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

此时已经将SpringApplication 对象new出来了,然后接着执行它的run方法,先总结下以上代码干了几个事情

  • this.primarySources 设置了主类集合
  • this.webApplicationType 设置了web容器类型
  • setInitializers 设置了ApplicationContextInitializer
  • setListeners 设置了ApplicationListener
  • mainApplicationClass 设置了入口类
  • 加载并且解析了所有的spring.factories文件并缓存起来,注意此时只是解析成属性。

我们发现一个奇怪的现象既然已经有了this.primarySources 为什么还要来个mainApplicationClass呢?mainApplicationClass目前看来除了日志打印以及banner之外没有什么其它作用。

接着看run方法

public ConfigurableApplicationContext run(String... args) {
		//计时器,不管它
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//告诉程序当前为无头模式,没有外设,如果需要调用外设接口需要你自己模拟,这些内容再awt包中做了,所以你只要告诉它有没有外设就可以了
		configureHeadlessProperty();
		//继续从spring.factories中获取对应的对象,此时listeners中有一个默认实现EventPublishingRunListener,如有需要这里是可进行扩展
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//遍历所有的listeners并调用它的starting方法,广播启动过程中的事件,注意这里是个广播器不是监听器,名字有点不好理解,实现了
		//ApplicationListener的接口就接收到对应事件之后执行对应操作,而我们前面也有设置了个listeners集合,为ApplicationListener
		//此处名字起的让人容易误解,明明是个广播器非要叫RunListener
		listeners.starting();
		try {
			//进行控制台参数解析,具体如何解析,此处不展开,后续对配置文件解析的文章在详细介绍
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			//此处配置个内省beanInfo时是否缓存的开关,true就缓存,false,就不缓存		
			configureIgnoreBeanInfo(environment);
			//打印banner,这个不介绍了比较简单的东西
			Banner printedBanner = printBanner(environment);
			//此处开始创建容器,根据给定的容器类型也就是上面获取到的webApplicationType创建对应的容器
			//servlet :org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
			//reactive:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
			//default:  org.springframework.context.annotation.AnnotationConfigApplicationContext 非web容器
			context = createApplicationContext();
			//异常解析器,如果出现异常了,会在被catch起来,然后通过解析器链进行解析。这个也是从spring.factories中获取的
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//上面创建了容器,这边对容器做一些初始化操作
			//1、设置环境变量env
			//2、执行postProcessApplicationContext 准备beanNameGenerator,resourceLoader,ApplicationConversionService
			//3、调用前面获取到的ApplicationContextInitializer
			//4、广播容器准备完成事件
			//5、添加了一个制定的单例就是banner打印用的
			//6、在加载前,设置是否允许bean被覆盖属性,默认false
			//7、开始加载,入口为main函数传入的primarySources,先创建个BeanDefinitionLoader,并将第二步准备的实例设置给他,
			//最后由BeanDefinitionLoader.load()承当所有的加载动作,加载过程也很长,先按下不表。
			//8、广播容器加载事件
			//到此容器准备完成		
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//开始执行spring的refresh方法,此处不多介绍了
			refreshContext(context);
			//容器refresh完成后留了个扩展,也不知道是不是扩展,这个方法需要去自定义启动类,有点奇怪
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			//然后广播启动完成事件
			listeners.started(context);
			//最后执行所有的runners,也就是实现了ApplicationRunner接口的类是最后执行,所以此时你可以做一些其它的动作,比如注册到注册中心,比如启动netty等等。
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}
		//在广播一个运行中的事件
		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

上述代码中在prepareContext阶段完成了第一阶段的beanDefinition注册,此处仅注册了第一bean 通过主类传进去的那个类。之后再refresh阶段,通过解析该类进行第二轮的beandefinition注册。这一部分属于spring-context的内容了。

在refresh阶段,因为主类作为一个配置类,自然也是需要进行一轮解析的,所以接下来的步骤就是解析@SpringbootApplication,此注解为一个复合注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

内容解析:

  • @SpringBootConfiguration 等同于@Configuration
  • @EnableAutoConfiguration
  @Target(ElementType.TYPE)
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Inherited
  @AutoConfigurationPackage
  @Import(AutoConfigurationImportSelector.class)
  public @interface EnableAutoConfiguration {

此处导入了一个AutoConfigurationImportSelector ,同时还通过@AutoConfigurationPackage导入了一个AutoConfigurationPackages.Registrar.class

  • 接下来是一个@ComponentScan注解

而我们知道在spring的配置类(单一类)的解析过程中的顺序是

  • 先解析Component系列的注解
  • 再解析@PropertySource
  • 接着解析@ComponentScan与@ComponentScans注解
  • 接着解析@Import
  • 接着解析ImportResource
  • 最后解析@Bean注解

而再Import中又分为几种类型

ImportSelector

DeferredImportSelector

ImportBeanDefinitionRegistrar

普通的import类

他们的加载顺序为,普通的类–>importSelector–deferredImportSelector–>ImportResource–>ImportBeanDefinitionRegistrar

综上所述spring.factories声明的配置类看来也不是垫底解析的,所以如果有遇到需要顺序的场景可以参照这个顺序来声明即可。

到此这篇关于Springboot启动流程详细分析的文章就介绍到这了,更多相关Springboot启动流程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Springboot启动流程详细分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Springboot启动流程详细分析

这篇文章主要介绍了SpringBoot启动过程的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-12-23

SpringBoot自动配置与启动流程详细分析

这篇文章主要介绍了SpringBoot自动配置原理分析,SpringBoot是我们经常使用的框架,那么你能不能针对SpringBoot实现自动配置做一个详细的介绍。如果可以的话,能不能画一下实现自动配置的流程图。牵扯到哪些关键类,以及哪些关键点
2022-11-13

JAVA中SpringBoot启动流程分析

这篇文章主要介绍了JAVA中SpringBoot启动流程分析的相关资料,需要的朋友可以参考下
2023-01-28

SpringBoot启动流程SpringApplication源码分析

这篇“SpringBoot启动流程SpringApplication源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“S
2023-07-05

SpringBoot之启动流程详解

SpringBoot是一个基于Spring框架的快速开发框架,旨在简化Spring应用程序的开发和部署。在本文中,我们将深入分析SpringBoot启动过程的源代码,并提供必要的解释和说明
2023-05-17

SpringBoot配置的加载流程详细分析

了解内部原理是为了帮助我们做扩展,同时也是验证了一个人的学习能力,如果你想让自己的职业道路更上一层楼,这些底层的东西你是必须要会的,这篇文章主要介绍了SpringBoot配置的加载流程
2023-01-06

Android 系统启动流分析 &amp; Zygote启动流程分析

本文是基于Android 7.1进行分析       Zygote在Android系统扮演着不可或缺的角色,Android系统的启动首先需要Zygote参与,比如启动SystemService , 还有一个就是孵化应用的进程,比如我们创建一
2022-06-06

linux启动流程详细介绍

linux启动流程简介 我们都知道,由于linux的稳定性,通常被作为服务器系统,要想称为一个PHP的高手,linux是必修之课。那么linux系统从开机到启动,中间到底都发生了什么?本文来简单探讨一下中间的神秘过程。 1、 BIOS加电自
2022-06-04

SpringBoot启动流程SpringApplication准备阶段源码分析

这篇文章主要为大家介绍了SpringBoot启动流程SpringApplication准备阶段源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-14

01-MyBatis启动流程分析

目录 MyBatis简单介绍 启动流程分析 简单总结 附录 MyBatis内置别名转换 参考 MyBatis简单
2017-03-19

ContentProvider启动流程源码分析

本文小编为大家详细介绍“ContentProvider启动流程源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“ContentProvider启动流程源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。C
2023-07-05

SpringBoot热部署启动关闭流程详解

Spring Boot启动热部署是一种技术,它能让开发者在不重启应用程序的情况下实时更新代码。这样可以提高开发效率,避免频繁重启应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
2023-05-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录