前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot启动流程(一)构造SpringApplication实例对象

springboot启动流程(一)构造SpringApplication实例对象

作者头像
技术从心
发布2021-02-03 12:01:45
5050
发布2021-02-03 12:01:45
举报
文章被收录于专栏:技术从心技术从心

来源:https://www.cnblogs.com/lay2017

启动入口

本文是springboot启动流程的第一篇,涉及的内容是SpringApplication这个对象的实例化过程。为什么从SpringApplication这个对象说起呢?我们先看一段很熟悉的代码片段

代码语言:javascript
复制
@SpringBootApplication
public class SpringBootLearnApplication {

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

}

springboot项目从一个main方法开始,main方法将会调用SpringApplication的run方法开始springboot的启动流程。所以,本文即从构造SpringApplication对象开始。

我们跟进SpringApplication的run方法

代码语言:javascript
复制
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

这是一个静态方法,入参有两个:

1)main方法所在的类,该类后续将被作为主要的资源来使用,比如通过该类获取到basePackage;

2)main方法的命令行参数,命令行参数可以通过main传入,也就意味着可以在springboot启动的时候设置对应的参数,比如当前是dev环境、还是production环境等。

第2行代码,run方法将调用另外一个内部run方法,并返回一个ConfigurableApplicationContext,预示着spring容器将在后续过程中创建。

跟进另一个run方法

代码语言:javascript
复制
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

run方法中先是构造了一个SpringApplication实例对象,而后调用了SpringApplication的成员方法run,这个run方法将包含springboot启动流程的核心逻辑。本文只讨论SpringApplication的实例化过程。

构造函数

跟进SpringApplication的构造函数中

代码语言:javascript
复制
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

构造函数调用了另外一个构造函数,继续跟进

代码语言:javascript
复制
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
     // 设置资源加载器
     this.resourceLoader = resourceLoader; 
     // 设置主要资源类
     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
     // 推断当前应用的类型
     this.webApplicationType = WebApplicationType.deduceFromClasspath();
 
     // 设置ApplicationContext的初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置Application监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 推断并设置主类
    this.mainApplicationClass = deduceMainApplicationClass();
}

构造过程主要包含:

1)推断当前应用类型

2)设置ApplicationContext初始化器、Application监听器

3)根据堆栈来推断当前main方法所在的主类

推断当前应用类型

WebApplicationType是一个枚举对象,枚举了可能的应用类型

代码语言:javascript
复制
public enum WebApplicationType {
 
     /**
      * 非web应用类型,不启动web容器
      */
     NONE,
     /**
      * 基于Servlet的web应用,将启动Servlet的嵌入式web容器
      */
    SERVLET,
    /**
     * 基于reactive的web应用,将启动reactive的嵌入式web容器
     */
    REACTIVE;

    // 省略...
}

deduceFromClasspath方法将会推断出当前应用属于以上三个枚举实例的哪一个,跟进方法

代码语言:javascript
复制
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;
     }
     // 如果Servlet或者ConfigurableWebApplicationContext不存在,那么就是非web应用
     for (String className : SERVLET_INDICATOR_CLASSES) {
         if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    // 否则都是Servlet的web应用
    return WebApplicationType.SERVLET;
}

推断过程将根据类路径中是否有指示性的类来判断

设置ApplicationContext初始化器、Application监听器

代码语言:javascript
复制
getSpringFactoriesInstances(ApplicationContextInitializer.class)

这个方法调用将会从META-INF/spring.factories配置文件中找到所有ApplicationContextInitializer接口对应的实现类配置,然后通过反射机制构造出对应的实例对象。

代码语言:javascript
复制
getSpringFactoriesInstances(ApplicationListener.class)

这个方法也是一样的做法,将会获取ApplicationListener接口的所有配置实例对象

有关于如何从spring.factories配置文件中获取配置并构造出实例对象请看:spring.factories配置文件的工厂模式

根据堆栈来推断当前main方法所在的主类

构造SpringApplication还有最后一步,推断出main方法所在的主类。我们跟进deduceMainApplicationClass方法

代码语言:javascript
复制
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方法落在哪个栈帧上,并通过forName来获取该类

总结

到这里本文就结束了,核心点就在于SpringApplication的实例化,可以看出最主要的就是做了应用类型的推断,后面的Application创建、Environment创建也会基于该类型。

我是“一生”,一个在国企苟且偷生的打工人,下期再见。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术从心 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 来源:https://www.cnblogs.com/lay2017
    • 启动入口
      • 构造函数
        • 推断当前应用类型
          • 根据堆栈来推断当前main方法所在的主类
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档