Springboot启动流程详细分析
作者:起风哥 发布时间:2023-11-29 00:23:10
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声明的配置类看来也不是垫底解析的,所以如果有遇到需要顺序的场景可以参照这个顺序来声明即可。
来源:https://blog.csdn.net/a807719447/article/details/128398469
猜你喜欢
- 基于有了OO的基础后,开始认真学习设计模式!设计模式是java设计中必不可少的!Apple.javapackage strategy;/**
- 假设目录结构是maven标准结构-src-target-test.jar(你需要更新的jar包)package com.foo.common
- 这几天自己研究了关于地手机上面开发安卓地图的问题,发现百度官方示例demo讲解百度持续定位方面还是讲解的有些不清楚,本人研究了几次之后将其弄
- 熬夜写完,尚有不足,但仍在努力学习与总结中,而您的点赞与关注,是对我最大的鼓励!在一些本地化项目开发当中,存在这样一种需求,即开发完成的项目
- 一、概述现在大多数的电商APP的详情页长得几乎都差不多,几乎都是上面一个商品的图片,当你滑动的时候,会有Tab悬浮在上面,这样做用户体验确实
- 本文实例为大家分享了Android实现支付宝支付密码输入界面的具体代码,供大家参考,具体内容如下效果图:主要代码:import java.u
- 最近开发项目中,有个在屏幕上任意拖动的悬浮窗功能,其实就是利用 WindowManager的api来完成这个需求,具体的实现的功能如下:1.
- 定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活。设计初衷:
- JDK8中有双冒号的用法,就是把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下。代码其实很简单:以前的
- 前文本章是关于Java流程控制语句的最全汇总,本篇为汇总中篇。流程是人们生活中不可或缺的一部分,它表示人们每天都在按照一定的流程做事。比如出
- 这篇文章主要介绍了JAVA实现账户取款和存款操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以
- 【程序1】题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?1.程序分析:可填在百位、十位、个位的数字都是1
- 目录查查询指定列查询所有列条件查询子查询根据业务逻辑添加条件连接查询增新增一条批量新增删改主要演示DynamicSql风格代码如何使用,基本
- 监听模式事件模型实现了监听模式,监听模式简单来说就是事件源经过事件的封装传给 * ,当事件源触发事件后, * 接收到事件对象可以回调事件的方
- 一、新时间日期API常用、重要对象介绍ZoneId: 时区ID,用来确定Instant和LocalDateTime互相转换的规则Instan
- 一般都在windows下开发的,现在部署到linux下:1,将项目达成war包(用eclipse,项目右键-->Export-->
- 引言应用 Java 的开源库,编写一个搜索引擎,这个引擎能爬取一个网站的内容。并根据网页内容进行深度爬取,获取所有相关的网页地址和内容,用户
- 1. JSCH简介JSch 是SSH2的一个纯Java实现。它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等。你可
- 很多App都有这种效果,特别一些电商类的App,顶部每隔几秒钟会向右翻页显示下张图片,用来作推广或者内容展示用的。今天来简单地模仿一下,还自
- redis redisson 集合操作相关类及接口Rlist:链表public interface RList<V> exten