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


    所有文章

    https://www.cnblogs.com/lay2017/p/11478237.html

    启动入口

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

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

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

    我们跟进SpringApplication的run方法

    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方法

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

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

    构造函数

    跟进SpringApplication的构造函数中

    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

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

    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是一个枚举对象,枚举了可能的应用类型

    public enum WebApplicationType {
     
         /**
          * 非web应用类型,不启动web容器
          */
         NONE,
         /**
          * 基于Servlet的web应用,将启动Servlet的嵌入式web容器
          */
        SERVLET,
        /**
         * 基于reactive的web应用,将启动reactive的嵌入式web容器
         */
        REACTIVE;
    
        // 省略...
    }

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

    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监听器

    getSpringFactoriesInstances(ApplicationContextInitializer.class)

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

    getSpringFactoriesInstances(ApplicationListener.class)

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

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

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

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

    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创建也会基于该类型。

  • 相关阅读:
    Oracle根据两点经纬度计算距离(转载)
    TCP小见解
    git describe功能实现
    UE中基本图形的原始大小是多大
    SQL多行合并与HTML组装,不转义特殊字符
    磁盘空间不足引起ftp报"553 Could not create file"
    一键安装包安装lnmp
    宝塔面板(Linux版)安装与使用
    Redhat7-yum本地源安装配置
    Oracle数据库多个表空间使用情况查询
  • 原文地址:https://www.cnblogs.com/lay2017/p/11478294.html
Copyright © 2020-2023  润新知