SpringBoot启动流程分析原理(一)

我们都知道 SpringBoot自问世以来,一直有一个响亮的口号”约定优于配置”,其实一种按约定编程的软件设计范式,目的在于减少软件开发人员在工作中的各种繁琐的配置,我们都知道传统的SSM框架的组合,会伴随着大量的繁琐的配置;稍有不慎,就可能各种bug,被人发现还以为我们技术很菜。而 SpringBoot的出现不仅大大提高的开发人员的效率,还能避免由于”手抖”带来的配置错误。

很多程序员都感慨 SpringBoot的到来大大解放了生产力,但是也有聪明的程序猿会多思考一下下, SpringBoot是怎么做到的约定的配置?它配置在了哪里?又是怎么启动的作用等等一系列的问号在跟女朋友花前月下的时候,依然会时不时冒出来。这严重影响了程序猿们的”幸”福生活,为了能广大”程序猿”同胞过上幸福美满的生活,今天咱么就来一起跟随源码探究下 SpringBoot到底是如何做到” 约定优于配置“的。

首先,我们先介绍下我们的演示的项目环境,我们先试用 Spring Initializr来创建一个 SpirngBoot 工程。我们使用的版本是 SpringBoot 2.4.3.RELEASE

SpringBoot启动流程分析原理(一)

接下来就只在 pom.xmL文件中添加一个web工程的依赖,是为了观察后面容器类型的源码。

        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>

这样我们的环境就准备好了。
我们跟着 SpringBoot的源码来探究它的启动流程,首先,先找到这个应用程序的入口主方法,在上面打一个断点:

SpringBoot启动流程分析原理(一)
启动之后,F7进入到 run()方法,我的电脑是点击F7(Step into)
SpringBoot启动流程分析原理(一)

到这里会执行 new SpringApplication(primarySources)创建spring应用对象,继续F7往下跟会执行 SpringApplication构造器

    //SpringApplication&#x6784;&#x9020;&#x5668;
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // &#x6B64;&#x5904;&#x7701;&#x7565;&#x6E90;&#x7801;...

        // &#x8D44;&#x6E90;&#x52A0;&#x8F7D;&#x5668;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        // 1.&#x53EF;&#x80FD;&#x7684;web&#x5E94;&#x7528;&#x7A0B;&#x5E8F;&#x7C7B;&#x578B;&#x7684;&#x7C7B;&#x578B;&#x3002;
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
        // 2.&#x8BBE;&#x7F6E;&#x521D;&#x59CB;&#x5316;&#x5E94;&#x7528;context
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 3.&#x8BBE;&#x7F6E;&#x521D;&#x59CB;&#x5316;&#x76D1;&#x542C;
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        // 4.&#x63A8;&#x6F14;&#x4E3B;&#x7A0B;&#x5E8F;&#x7C7B;
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

很多不为人知的事情都是发生在这个对象初始化的时候,这里我们都来一一解密

    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        } else {
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }
            // &#x8FD9;&#x91CC;&#x662F;&#x6211;&#x4EEC;&#x6D4B;&#x8BD5;web&#x5BB9;&#x5668;
            return SERVLET;
        }
    }

1. 推断web 应用类型

这段代码是来推断我们的应用是哪种web应用程序

SpringBoot启动流程分析原理(一)
public enum WebApplicationType{

    NONE&#xFF0C;// &#x4E0D;&#x662F;web&#x5E94;&#x7528;

    SERVLET&#xFF0C;// servlet&#x5BB9;&#x5668;

    REACTIVE; // &#x53CD;&#x5E94;&#x578B;web&#x5E94;&#x7528;&#xFF08;webflux&#xFF09;
}

当然一开始我们加入了web的依赖,所以我们是 servlet 容器。

2. 初始化应用上下文

在设置初始化应用context的时候,是先执行了 getSpringFactoriesInstances&#xFF08;ApplicationContextInitializer.class&#xFF09;方法,参数是 ApplicationContextInitializer.class字节码对象。

    private <t> Collection<t> getSpringFactoriesInstances(Class<t> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        // Use names and ensure unique to protect against dupLicates
        Set<string> names = new LinkedHashSet(
                 // &#x52A0;&#x8F7D;ApplicationContextInitializer.class&#x7C7B;&#x578B;&#x7684;&#x7C7B;
                 // &#x8FD9;&#x91CC;&#x4F20;&#x5165;&#x5C31;&#x662F;&#x53C2;&#x6570; ApplicationContextInitializer.class
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // &#x5B9E;&#x4F8B;&#x5316;&#x52A0;&#x8F7D;&#x5230;&#x7684;&#x7C7B;
        List<t> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        // &#x8FD4;&#x56DE;
        return instances;
    }
</t></string></t></t></t>

我们先来看看他是如何加载到这些类

SpringBoot启动流程分析原理(一)
    private static Map<string, list<string>> loadSpringFactories(ClassLoader classLoader) {
        // &#x4ECE;&#x7F13;&#x5B58;&#x4E2D;&#x62FF;
        Map<string, list<string>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                // &#x4ECE;&#x8D44;&#x6E90;&#x8DEF;&#x5F84;&#x4E0B;&#x52A0;&#x8F7D;
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                // &#x8FD4;&#x56DE;&#x6240;&#x6709;&#x7684;&#x52A0;&#x8F7D;&#x7684;&#x7C7B;
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }
</string,></string,>

这里有两个加载配置类的地方其实都指向了 META-INF/spring.factories,通过断点我们可以看到应用程序是加载了以下几个jar下的 spring.factories 文件。

双击Shifi搜索spring.factories可以看到它存在于以下工程中

SpringBoot启动流程分析原理(一)

spring-boot-2.4.3.RELEASE.jar 下的 spring.factories (截图未完整截取)

SpringBoot启动流程分析原理(一)

spring-boot-autoconfigure-2.4.3.RELEASE.jar 下的 spring.factories

SpringBoot启动流程分析原理(一)

spring-beans-2.4.3.RELEASE.jar 下的 spring.factories

SpringBoot启动流程分析原理(一)

从Map中根据 org.springframework.context.ApplicationContextInitializer 的类型拿到需要的类初始化类,断点进入 getOrDefault(factoryClassName,Collections.emptyList());方法

SpringBoot启动流程分析原理(一)
之后就是把加载到的需要初始化的类进行实例化添加到一个集合中等待备用
SpringBoot启动流程分析原理(一)

3. 初始化监听器类

最关键的的还是这句

SpringBoot启动流程分析原理(一)
当我们跟进去之后,会发现在初始化监听类的时候和上面初始化应用上下文是一样的代码。唯一不同的是 getSpringFactoriesInstances(ApplicationListener.class))传进去的是·ApplicationListener.class 所以这里就不再赘述。

4. 推演主程序类

也就是这个最关键的代码了
this.mainApplicationClass = this.deduceMainApplicationClass();

SpringBoot启动流程分析原理(一)

到这里就完成了 SpringBoot启动过程中初始化SpringApplication 的过程。

这篇文章主要是给大家说了下 SpringBoot启动过程中初始化 SpringApplication的流程,大致可以分为四个步骤∶

  1. 推演web应用的类型(如果没有加web依赖类型NONE)
  2. 初始化 ApplicationContextInitializer
  3. 初始化 ApplicationListener
  4. 推演出主程序类
    通过这样四个步骤就完成了第一步 SpringApplication 的初始化过程。

Original: https://www.cnblogs.com/reminis/p/14486867.html
Author: 小懒编程日记
Title: SpringBoot启动流程分析原理(一)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/571498/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • spring注入

    出处:http://cnblogs.com/daishuguang Original: https://www.cnblogs.com/daishuguang/p/5201332….

    Java 2023年5月30日
    062
  • 常用正则表达式

    一、校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数字:^\d{m,n}$ 零和非零开头的数字:^(0|[1-9…

    Java 2023年6月7日
    063
  • 点赞和取消点赞实现Redis缓存(只思路)

    思路:点赞、取消点赞 ——> Redis ——> (每两个小时)存到数据库(MySQL),所以就相当于每次查询或者存储都需要先经过Redis,而查询的目的是为了判断用户…

    Java 2023年6月7日
    065
  • springboot项目记录3用户注册界面

    九、注册-前端页面 1.在register页面中编写发送请求的方法,采用点击事件来完成。选中对应的按钮(JQuery下的)(( ” 选 择 器 ” ) ) …

    Java 2023年6月7日
    049
  • 基于TCP/IP协议,定义原始的字节流协议传输Student类

    在分布式系统中,不同节点之间需要进行通信来实现一致性,例如:在投票选举阶段,候选者需要为所有其他节点发送拉票请求,拉票请求中包含着自己的网络地址和任期号,也就是说,我们需要发送一个…

    Java 2023年6月5日
    0116
  • Java实现栈

    package algorithm; import java.util.Arrays;import java.util.Iterator; /** @author Administ…

    Java 2023年6月15日
    065
  • C# 多线程详解 Part.04(Lock、Monitor、生产与消费)

    /// /// 被操作的对象 /// public class Cell { /// /// Cell 对象里的内容 /// int cellContents; /// /// 状…

    Java 2023年5月29日
    062
  • TransmittableThreadLocal和@Async优雅的记录操作日志

    此文主要讲解: 如何实现操作记录 如何将TransmittableThreadLocal和@Async搭配使用 TransmittableThreadLocal阿里的一个开源组件,…

    Java 2023年6月5日
    086
  • 《回炉重造》——泛型

    泛型 前言 以前学习到「泛型」的时候,只是浅浅的知道可以限制类型,并没有更深入理解,可以说基础的也没理解到位,只是浮于表面,所以,现在回炉重造,重学泛型!打好基础! 什么是泛型? …

    Java 2023年6月10日
    085
  • 浅谈kali : arpspoof工具原理

    介绍 arpspoof是一个通过ARP协议伪造数据包实现中间人攻击的kali工具。 中间人攻击虽然古老,但仍处于受到黑客攻击的危险中,可能会严重导致危害服务器和用户。仍然有很多变种…

    Java 2023年6月7日
    071
  • Java是编译性语言还是解释型语言 ?

    0.先说明一下怎么突然想到这个问题了. 大概思路应该是这个样子的 … JVM –> Java内存区域 –>运行时数据区域 &#821…

    Java 2023年5月29日
    068
  • nginx常用常忘的配置方法

    参考网址 具体应用 location /images/ { root /opt/html/; try_files $uri $uri/ /images/default.gif; }…

    Java 2023年5月30日
    060
  • (九)、SpringBoot整合Swagger2、Swagger3

    (九)、SpringBoot整合Swagger2、Swagger3 一、整合 Swagger2 1、maven 依赖: io.springfox springfox-swagger…

    Java 2023年5月29日
    0111
  • uu467 图片选择修改点

    com.imagepicker.ImagePickerModule if (this.options.useFrontCamera) { System.err.println(&q…

    Java 2023年5月30日
    057
  • Java StringBuffer 和 StringBuilder 类区别

    当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。 和 String 类不同的是,StringBuffer 和 StringBuil…

    Java 2023年5月29日
    052
  • Typora文件上传博客园(插件dotnet-cnblog)

    Typora文件上传博客园 写在前面:无论是学习还是工作中,写博客都是必备的技能之一。你的博客里,可以记录自己的学习笔记,可以记录自己在学习过程中的心得体会或疑难问题。接下来介绍一…

    Java 2023年5月30日
    098
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球