【java】Spring Boot启动流程

news2024/11/23 19:07:04

Spring Boot启动流程目录

  • 一、简述
  • 二、注解
    • @SpirngBootApplication注解
  • 三、启动方法
    • 1、创建SpringApplication实例
      • 1.1、WebApplicationType
      • 1.2、getBootstrapRegistryInitializersFromSpringFactories
      • 1.3、setInitializers && setListeners
      • 1.4、deduceMainApplicationClass
    • 2、run方法
      • 2.1、configureHeadlessProperty
      • 2.2、prepareEnvironment
      • 2.3、printBanner
      • 2.4、createApplicationContext
      • 2.6、refresh
      • 2.7、onRefresh
      • 2.8、afterRefresh
      • 2.9、停止计时并打印启动完毕相关日志
      • 2.10、started
      • 2.11、callRunners
      • 2.12、running
  • 四、总结

一、简述

Spring Boot启动流程分析使用版本SpringBoot VERSION:版本 2.5.5-SNAPSHOT。

Spring Boot项目最简单的Application启动类。
在这里插入图片描述
可以看出Application启动类中,包含了@SpringBootApplication 注解和 SpringApplication.run 启动方法,所以SpringBoot的启动可以分解为 注解 和 启动方法 两大过程,而仔细看启动类中还引入了一个【org.springframework.boot.SpringApplication】包,所以启动方法中又可以分为两个阶段即 创建SpringApplication 实例 和 执行run方法。

二、注解

注解暂且简单了解,暂不深入。

@SpirngBootApplication注解

进入@SpringBootApplication注解内。
在这里插入图片描述

从@SpringBootApplication注解内部可以发现,它虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

  • @SpringBootConfiguration(@SpringBootConfiguration注解点开查看发现里面还是应用了@Configuration)->Spring IOC容器配置类。
  • @EnableAutoConfiguration ->使用@Import将所有符合自动配置条件的bean定义加载到IOC容器。
  • @ComponentScan ->自动扫描并加载符合条件的组件或者bean定义,默认扫描SpringApplication的run方法里的class

所在的包路径下文件,所以通常将该启动类放到根包路径下。
即 @SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。

三、启动方法

启动方法中分为两个阶段即 创建SpringApplication 实例 和 执行run方法。

1、创建SpringApplication实例

从启动类中的run方法跟进去,SpringApplication.run -> return run -> return new SpringApplication(primarySources).run(args) -> this(null, primarySources) -> SpringApplication。

其中:return new SpringApplication(primarySources).run(args) ,如果跟new SpringApplication(primarySources) 方法则是启动方法中的第一阶段即创建SpringApplication实例,跟run(args) 方法进去就是启动方法中的第二阶段。
在这里插入图片描述

public SpringApplication(ResourceLoader resourceLoader, Class<?>… primarySources)

/**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     *
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 初始化类加载器
        this.resourceLoader = resourceLoader;
        // Assert 断言非空,若传入的class参数为null则打印异常并退出初始化
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 获取main方法中的args,初始化启动时配置的额外参数集合
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 判断项目启动类型:NONE/SERVLET/REACTIVE
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 从 Spring 工厂获取 Bootstrap Registry Initializers
        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        // 获取 Spring 工厂实例 -> 容器上下文相关的初始化
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 获取 Spring 工厂实例 -> 设置应用程序监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 推导出主应用程序类,即从当前的栈信息中寻找main所在主类:com.iot.SpringBootLoveApplication
        this.mainApplicationClass = deduceMainApplicationClass();
    }

1.1、WebApplicationType

WebApplicationType 判断项目类型。
在这里插入图片描述

public enum WebApplicationType

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot;

import org.springframework.util.ClassUtils;

/**
 * An enumeration of possible types of web application.
 *
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 */
public enum WebApplicationType {

    /**
     * The application should not run as a web application and should not start an
     * embedded web server.
     */
    NONE,

    /**
     * The application should run as a servlet-based web application and should start an
     * embedded servlet web server.
     */
    SERVLET,

    /**
     * The application should run as a reactive web application and should start an
     * embedded reactive web server.
     */
    REACTIVE;

    private static final String[] SERVLET_INDICATOR_CLASSES = {"javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext"};

    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

    /**
     * deduceFromClasspath
     * 依次循环遍历当前应用中是否存在相关的类来判断最终应用的启动类型
     *
     * @return
     */
    static WebApplicationType deduceFromClasspath() {
        /**
         * REACTIVE:响应式WEB项目
         * 若启动类型为REACTIVE,
         * 则类路径下存在 org.springframework.web.reactive.DispatcherHandler 类
         * 并且不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
         * 两者指的是SpringMVC/Tomcat和jersey容器
         */
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        /**
         * NONE:非WEB项目,就是一个最简单的Springboot应用
         * 若启动类型为NONE
         * 则类路径下 javax.servlet.Servlet 和org.springframework.web.context.ConfigurableWebApplicationContext都不存在
         */
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        /**
         * SERVLET:SERVLET WEB 项目
         * 若启动类型为Servlet,则必须有SERVLET_INDICATOR_CLASSES中的javax.servlet.Servlet
         * 和org.springframework.web.context.ConfigurableWebApplicationContext
         */
        return WebApplicationType.SERVLET;
    }

    static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
        if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
            return WebApplicationType.SERVLET;
        }
        if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
            return WebApplicationType.REACTIVE;
        }
        return WebApplicationType.NONE;
    }

    private static boolean isAssignable(String target, Class<?> type) {
        try {
            return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
        } catch (Throwable ex) {
            return false;
        }
    }

}

1.2、getBootstrapRegistryInitializersFromSpringFactories

getBootstrapRegistryInitializersFromSpringFactories方法从spring.factories 中获取 BootstrapRegistryInitializer。
在这里插入图片描述

private List getBootstrapRegistryInitializersFromSpringFactories()

private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories(){
        ArrayList<BootstrapRegistryInitializer> initializers=new ArrayList<>();
        /**
         * 从spring.factories 中获取Bootstrapper集合,
         * 然后遍历转化为BootstrapRegistryInitializer,再存入 initializers
         */
        getSpringFactoriesInstances(Bootstrapper.class).stream()
        .map((bootstrapper)->((BootstrapRegistryInitializer)bootstrapper::initialize))
        .forEach(initializers::add);
        /**
         * 从spring.factories 中获取BootstrapRegistryInitializer集合,再存入 initializers
         * getSpringFactoriesInstances 该方法在整个启动流程中会频繁出现,下面集中介绍
         */
        initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        return initializers;
        }

1.3、setInitializers && setListeners

setInitializers && setListeners 分别是容器上下文初始化 & 监听器初始化。

容器上下文初始化setInitializers 和监听器初始化setListeners 都是调用了getSpringFactoriesInstances() 方法,从spring.factories中获取配置。不同的是传给它的type参数,主要有一下几种类型。

  • ApplicationContextInitializer.class 上下文相关
  • ApplicationListener.class 监听器相关
  • SpringApplicationRunListener.class 运行时监听器
  • SpringBootExceptionReporter.class 异常类相关
    在这里插入图片描述

private Collection getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object… args)

/**
     * The location to look for factories.
     * <p>Can be present in multiple JAR files.
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


    /**
     * 从spring.factories中获取配置
     */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // Use names and ensure unique to protect against duplicates
        /**
         * 加载各jar包中的"META-INF/spring.factories"配置
         * 其中SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法
         * 是获取spring.factories配置文件中已经配置的指定类型的的实现类集合
         * 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories
         */
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 通过反射创建这些类
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }


    /**
     * Load the fully qualified class names of factory implementations of the
     * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
     * class loader.
     * <p>As of Spring Framework 5.3, if a particular implementation class name
     * is discovered more than once for the given factory type, duplicates will
     * be ignored.
     *
     * @param factoryType the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading resources; can be
     *                    {@code null} to use the default
     * @throws IllegalArgumentException if an error occurs while loading factory names
     * @see #loadFactories
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }


    /**
     * Springboot自动配置的秘密
     * Springboot在启动时读取了所有starter jar包里的META-INF/spring.factories配置文件,实现了所谓的自动化配置
     * 这里jar包里的都是默认配置,后续Springboot也会从xml、yaml文件中的用户配置去覆盖同名的配置。
     * 另外,这里的缓存配置是保存在一个map类型的cache中,其中的key键对应上面提到的各种Type类型,value就是Type的各种初始jar包里的同类型Java类。
     */
    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        // 获取相应类加载器中内容
        Map<String, List<String>> result = cache.get(classLoader);
        // 存在则直接返回类加载器中内容
        if (result != null) {
            return result;
        }
        // 不存在则初始化类加载器中内容
        result = new HashMap<>();
        try {
            /**
             * 获取资源 -> META-INF/spring.factories 列表
             * 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories
             */
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            // 可能存在多个META-INF/spring.factories 文件,循环加载
            while (urls.hasMoreElements()) {
                // 获取 META-INF/spring.factories 文件URL地址
                URL url = urls.nextElement();
                // 加载资源
                UrlResource resource = new UrlResource(url);
                // 加载资源配置
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                // key:value形式循环配置
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    // 逗号分隔列表到字符串数组
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    // 循环value中子项到列表中
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                                .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            // 列表去重
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            // 列表保存
            cache.put(classLoader, result);
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }


    /**
     * 反射创建实现类
     */
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
                                                       ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

1.4、deduceMainApplicationClass

deduceMainApplicationClass 推导主应用程序类。
在这里插入图片描述

/**
     * 推导主应用程序类
     * @return
     */
    private Class<?> deduceMainApplicationClass() {
        try {
            // 获取当前的栈信息
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                // 获取main方法所在的类class,此处即com.iot.SpringBootLoveApplication
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

private Class<?> deduceMainApplicationClass()
View Code

2、run方法

初始化完SpringApplication 就可以运行他的run方法了,也就是启动方法中的第二阶段。

在这里插入图片描述

public ConfigurableApplicationContext run(String… args)

/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     *
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        // 启动一个秒表计时器,用于统计项目启动时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 创建启动上下文对象即spring根容器
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        // 定义可配置的应用程序上下文变量
        ConfigurableApplicationContext context = null;
        /**
         * 设置jdk系统属性
         * headless直译就是无头模式,
         * headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
         */
        configureHeadlessProperty();
        /**
         * 获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法
         * 从spring.factories中获取配置
         */
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 启动监听器
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 包装默认应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //
            /**
             * 准备环境 prepareEnvironment 是个硬茬,里面主要涉及到
             * getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
             * environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
             */
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            // 配置忽略的 bean
            configureIgnoreBeanInfo(environment);
            // 打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
            Banner printedBanner = printBanner(environment);
            // 创建 IOC 容器
            context = createApplicationContext();
            // 设置一个启动器,设置应用程序启动
            context.setApplicationStartup(this.applicationStartup);
            // 配置 IOC 容器的基本信息 (spring容器前置处理)
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            /**
             * 刷新IOC容器
             * 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
             */
            refreshContext(context);
            /**
             * 留给用户自定义容器刷新完成后的处理逻辑
             * 刷新容器后的扩展接口(spring容器后置处理)
             */
            afterRefresh(context, applicationArguments);
            // 结束计时器并打印,这就是我们启动后console的显示的时间
            stopWatch.stop();
            if (this.logStartupInfo) {
                // 打印启动完毕的那行日志
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
            listeners.started(context);
            // 执行runner,遍历所有的 runner,调用 run 方法
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 异常处理,如果run过程发生异常
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 所有的运行监听器调用 running() 方法,监听应用上下文
            listeners.running(context);
        } catch (Throwable ex) {
            // 异常处理
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        // 返回最终构建的容器对象
        return context;
    }

2.1、configureHeadlessProperty

configureHeadlessProperty 设置headless无头模式。
在这里插入图片描述

private void configureHeadlessProperty()

 1     private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
 2     
 3     /**
 4      * headless直译就是无头模式,
 5      * headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
 6      */
 7     private void configureHeadlessProperty() {
 8         // SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
 9         System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
10                 System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
11     }

2.2、prepareEnvironment

prepareEnvironment 准备环境是个硬茬,里面主要涉及到getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfilesenvironmentPrepared、bindToSpringApplication、attach诸多方法。

在这里插入图片描述

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments)

/**
     * 准备环境
     *
     * @param listeners
     * @param bootstrapContext
     * @param applicationArguments
     * @return
     */
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                       DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // Create and configure the environment 创建和配置环境
        // 根据项目类型建环境ConfigurableEnvironment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 从环境中获取并设置 PropertySources 和 activeProfiles
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 把 PropertySources 设置在自己PropertySources的第一个位置
        ConfigurationPropertySources.attach(environment);
        /**
         * 运行监听器调用
         * 广播事件,listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)
         * 发布事件通知所有的监听器当前环境准备完成
         */
        listeners.environmentPrepared(bootstrapContext, environment);
        // 移动 defaultProperties 属性源到环境中的最后一个源
        DefaultPropertiesPropertySource.moveToEnd(environment);
        // 断言 抛异常
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        // 与容器绑定当前环境
        bindToSpringApplication(environment);
        // 若非web环境,将环境转换成StandardEnvironment
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        // 配置PropertySources对它自己的递归依赖
        ConfigurationPropertySources.attach(environment);
        return environment;
    }


    /**
     * 获取或创建环境Environment
     *
     * @return
     */
    private ConfigurableEnvironment getOrCreateEnvironment() {
        // 存在则直接返回
        if (this.environment != null) {
            return this.environment;
        }
        /**
         * 根据webApplicationType创建对应的Environment
         */
        switch (this.webApplicationType) {
            // SERVLET WEB 项目
            case SERVLET:
                return new ApplicationServletEnvironment();
            // REACTIVE:响应式WEB项目
            case REACTIVE:
                return new ApplicationReactiveWebEnvironment();
            // 非WEB项目,就是一个最简单的Springboot应用
            default:
                return new ApplicationEnvironment();
        }
    }

    /**
     * 从环境中获取并设置 PropertySources 和 activeProfiles
     * 将配置任务按顺序委托给configurePropertySources和configureProfiles
     * Template method delegating to
     * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
     * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
     * Override this method for complete control over Environment customization, or one of
     * the above for fine-grained control over property sources or profiles, respectively.
     *
     * @param environment this application's environment
     * @param args        arguments passed to the {@code run} method
     * @see #configureProfiles(ConfigurableEnvironment, String[])
     * @see #configurePropertySources(ConfigurableEnvironment, String[])
     */
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            environment.setConversionService(new ApplicationConversionService());
        }
        // 配置PropertySources
        configurePropertySources(environment, args);
        // 配置Profiles
        configureProfiles(environment, args);
    }

    /**
     * 配置PropertySources
     * Add, remove or re-order any {@link PropertySource}s in this application's
     * environment.
     *
     * @param environment this application's environment
     * @param args        arguments passed to the {@code run} method
     * @see #configureEnvironment(ConfigurableEnvironment, String[])
     */
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        // 初始化 defaultProperties
        if (!CollectionUtils.isEmpty(this.defaultProperties)) {
            // 存在的话将其放到最后位置
            DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
        }
        /**
         * 存在命令行参数,则解析它并封装进SimpleCommandLinePropertySource对象
         * 同时将此对象放到sources的第一位置(优先级最高)
         */
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(
                        new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                // 放到首位
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

    /**
     * 配置Profiles
     *
     * @param environment
     * @param args
     */
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        /**
         * 保证environment的activeProfiles属性被初始化了。从PropertySources中查找spring.profiles.active属性
         * 存在则将其值添加activeProfiles集合中。
         * 配置应用环境中的哪些配置文件处于激活状态(或默认激活)
         * 可以通过spring.profiles.active属性在配置文件处理期间激活其他配置文件
         * 就是我们项目中通常配置的dev、sit、prod等环境配置信息设置哪些Profiles是激活的。
         */
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        // 如果存在其他的Profiles,则将这些Profiles放到第一的位置
        Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }

    /**
     * 运行监听器调用
     *
     * @param bootstrapContext
     * @param environment
     */
    void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        doWithListeners("spring.boot.application.environment-prepared",
                (listener) -> listener.environmentPrepared(bootstrapContext, environment));
    }

    /**
     * 运行监听器调用
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     *
     * @param environment the environment
     * @deprecated since 2.4.0 for removal in 2.6.0 in favor of
     * {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
     */
    @Deprecated
    default void environmentPrepared(ConfigurableEnvironment environment) {
        for (SpringApplicationRunListener listener : this.listeners) {
            // 广播ApplicationEnvironmentPreparedEvent事件,后面再看
            listener.environmentPrepared(environment);
        }
    }

    /**
     * 与容器绑定当前环境
     * Bind the environment to the {@link SpringApplication}.
     *
     * @param environment the environment to bind
     */
    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            // 将environment绑定到SpringApplication
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception ex) {
            throw new IllegalStateException("Cannot bind to SpringApplication", ex);
        }
    }

    /**
     * 配置PropertySources对它自己的递归依赖
     * Attach a {@link ConfigurationPropertySource} support to the specified
     * {@link Environment}. Adapts each {@link PropertySource} managed by the environment
     * to a {@link ConfigurationPropertySource} and allows classic
     * {@link PropertySourcesPropertyResolver} calls to resolve using
     * {@link ConfigurationPropertyName configuration property names}.
     * <p>
     * The attached resolver will dynamically track any additions or removals from the
     * underlying {@link Environment} property sources.
     *
     * @param environment the source environment (must be an instance of
     *                    {@link ConfigurableEnvironment})
     * @see #get(Environment)
     */
    public static void attach(Environment environment) {
        // 判断environment是否是ConfigurableEnvironment的实例
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        // 从environment获取PropertySources
        MutablePropertySources sources = ((ConfigurableEnvironment) environment)
                .getPropertySources();
        PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
        if (attached != null && attached.getSource() != sources) {
            sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
            attached = null;
        }
        if (attached == null) {
            // 将sources封装成ConfigurationPropertySourcesPropertySource对象,并把这个对象放到sources的第一位置
            sources.addFirst(new ConfigurationPropertySourcesPropertySource(
                    ATTACHED_PROPERTY_SOURCE_NAME,
                    new SpringConfigurationPropertySources(sources)));
        }
    }

2.3、printBanner

printBanner 打印SpringBoot标志。printBanner(environment)方法就是打印Banner,Banner就是项目启动时看到的那个logo。在工程项目src/main/resources路径下下放入名字是banner的文件,后缀后可以是SpringApplicationBannerPrinter.java类里的{ “gif”, “jpg”, “png” },或者是txt、图片也可以的,但是图片打印时会字符化,而不是打印图片本身。自定义banner链接

在这里插入图片描述

private Banner printBanner(ConfigurableEnvironment environment)

/**
     * 打印SpringBoot标志
     * banner的输出默认有三种种模式,LOG、CONSOLE、OFF。
     * 1. LOG:将banner信息输出到日志文件。
     * 2. CONSOLE:将banner信息输出到控制台。
     * 3. OFF:禁用banner的信息输出。
     *
     * @param environment
     * @return
     */
    private Banner printBanner(ConfigurableEnvironment environment) {
        // 判断Banner的模式是否关闭,如果关闭直接返回。
        if (this.bannerMode == Banner.Mode.OFF) {
            return null;
        }
        ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
                : new DefaultResourceLoader(null);
        // 创建SpringApplicationBannerPrinter 打印类
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
        // LOG:将banner信息输出到日志文件
        if (this.bannerMode == Mode.LOG) {
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        //banner没有关闭且没有指定是写到log文件中 将banner信息输出到控制台
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

    /**
     * 打印
     *
     * @param environment
     * @param sourceClass
     * @param logger
     * @return
     */
    Banner print(Environment environment, Class<?> sourceClass, Log logger) {
        // 获取banner内容
        Banner banner = getBanner(environment);
        try {
            logger.info(createStringFromBanner(banner, environment, sourceClass));
        } catch (UnsupportedEncodingException ex) {
            logger.warn("Failed to create String for banner", ex);
        }
        return new PrintedBanner(banner, sourceClass);
    }

    /**
     * 获取banner内容
     *
     * @param environment
     * @return
     */
    private Banner getBanner(Environment environment) {
        Banners banners = new Banners();
        // 图片类型的banner内容
        banners.addIfNotNull(getImageBanner(environment));
        // 文本类型的banner内容
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        }
        if (this.fallbackBanner != null) {
            return this.fallbackBanner;
        }
        return DEFAULT_BANNER;
    }

    static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
    static final String DEFAULT_BANNER_LOCATION = "banner.txt";

    /**
     * 文本类型的banner内容获取
     *
     * @param environment
     * @return
     */
    private Banner getTextBanner(Environment environment) {
        /**
         * 拿到自定义配置的banner文件地址
         * BANNER_LOCATION_PROPERTY = "spring.banner.location"
         * DEFAULT_BANNER_LOCATION = "banner.txt";
         */
        String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
        Resource resource = this.resourceLoader.getResource(location);
        try {
            if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
                return new ResourceBanner(resource);
            }
        } catch (IOException ex) {
            // Ignore
        }
        return null;
    }

2.4、createApplicationContext

createApplicationContext创建IOC容器。

在这里插入图片描述

protected ConfigurableApplicationContext createApplicationContext()

/**
     * 创建 IOC 容器
     * A default {@link ApplicationContextFactory} implementation that will create an
     * appropriate context for the {@link WebApplicationType}.
     */
    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            // 根据当前应用的类型创建 IOC 容器
            switch (webApplicationType) {
                // Web 应用环境对应 AnnotationConfigServletWebServerApplicationContext
                case SERVLET:
                    return new AnnotationConfigServletWebServerApplicationContext();
                // 响应式编程对应 AnnotationConfigReactiveWebServerApplicationContext
                case REACTIVE:
                    return new AnnotationConfigReactiveWebServerApplicationContext();
                // 默认为 Spring 环境 AnnotationConfigApplicationContext
                default:
                    return new AnnotationConfigApplicationContext();
            }
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                    + "you may need a custom ApplicationContextFactory", ex);
        }
    };

    /**
     * 设置一个启动器
     * Set the {@link ApplicationStartup} for this application context.
     * <p>This allows the application context to record metrics
     * during startup.
     * @param applicationStartup the new context event factory
     * @since 5.3
     */
    void setApplicationStartup(ApplicationStartup applicationStartup);

2.5、prepareContext
prepareContext 配置 IOC 容器的基本信息。

在这里插入图片描述

private void prepareContext(参数此处省略)

1     /**
 2      * 准备IOC容器基本信息
 3      * @param bootstrapContext
 4      * @param context
 5      * @param environment
 6      * @param listeners
 7      * @param applicationArguments
 8      * @param printedBanner
 9      */
10     private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
11                                 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
12                                 ApplicationArguments applicationArguments, Banner printedBanner) {
13         // 设置容器环境,包括各种变量
14         context.setEnvironment(environment);
15         /**
16          * 后置处理流程
17          * 设置IOC容器的 bean 生成器和资源加载器
18          */
19         postProcessApplicationContext(context);
20         /**
21          * 获取所有的初始化器调用 initialize() 方法进行初始化
22          * 执行容器中的ApplicationContextInitializer(包括从 spring.factories和自定义的实例)初始化
23          */
24         applyInitializers(context);
25         /**
26          * 触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
27          * 所有的运行监听器调用 environmentPrepared() 方法,EventPublishingRunListener 发布事件通知 IOC 容器准备完成
28          */
29         listeners.contextPrepared(context);
30         bootstrapContext.close(context);
31         // 打印启动日志
32         if (this.logStartupInfo) {
33             logStartupInfo(context.getParent() == null);
34             logStartupProfileInfo(context);
35         }
36         // Add boot specific singleton beans
37         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
38         // 注册添加特定的单例bean
39         beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
40         if (printedBanner != null) {
41             beanFactory.registerSingleton("springBootBanner", printedBanner);
42         }
43         if (beanFactory instanceof DefaultListableBeanFactory) {
44             ((DefaultListableBeanFactory) beanFactory)
45                     .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
46         }
47         if (this.lazyInitialization) {
48             context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
49         }
50         // Load the sources
51         // 加载所有资源
52         Set<Object> sources = getAllSources();
53         // 断言资源费控
54         Assert.notEmpty(sources, "Sources must not be empty");
55         // 创建BeanDefinitionLoader,加载启动类,将启动类注入容器
56         load(context, sources.toArray(new Object[0]));
57         // 触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
58         listeners.contextLoaded(context);
59     }

2.6、refresh

refresh 刷新应用上下文,即刷新Spring上下文信息refreshContext。这里会涉及Spring容器启动、SpringBoot自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat。

在这里插入图片描述

private void refreshContext(ConfigurableApplicationContext context)

/**
     * 刷新应用上下文
     *
     * @param context
     */
    private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            // 判断是否注册关闭的钩子,是则注册钩子
            shutdownHook.registerApplicationContext(context);
        }
        refresh(context);
    }

    /**
     * Refresh the underlying {@link ApplicationContext}.
     *
     * @param applicationContext the application context to refresh
     */
    protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }

    /**
     * 刷新IOC容器
     *
     * @throws BeansException
     * @throws IllegalStateException
     */
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // Prepare this context for refreshing. 准备刷新上下文
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory. 通知子类刷新内部工厂
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context. 准备Bean工厂
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                // 允许在上下文子类中对bean工厂进行后处理,这部分涉及Web服务器的启动,如servlet
                postProcessBeanFactory(beanFactory);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                // 调用在上下文中注册为 bean 的工厂处理器
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation. 注册拦截 bean 创建的 bean 处理器
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context. 初始化此上下文的消息源
                initMessageSource();

                // Initialize event multicaster for this context. 为该上下文初始化事件多播器
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses. 初始化特定上下文子类中的其他特殊 bean
                /**
                 * SpringBoot 一键启动web工程的关键方法
                 * 创建 WebServer启动Web服务
                 * SpringBoot启动内嵌的 Tomcat 首先要在pom文件配置内嵌容器为tomcat
                 * SpringBoot 嵌入式 Servlet 容器,默认支持的 webServe:Tomcat、Jetty、Undertow
                 *          <exclusion>
                 *             <groupId>org.springframework.boot</groupId>
                 *             <artifactId>spring-boot-starter-tomcat</artifactId>
                 *         </exclusion>
                 */
                onRefresh();

                // Check for listener beans and register them. 检查侦听器 bean 并注册
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons. 实例化所有剩余的(非延迟初始化)单例
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event. 发布事件
                finishRefresh();
            } catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.  销毁bean
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            } finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }

2.7、onRefresh

onRefresh方法中创建WebServer、创建Tomcat对象,是SpringBoot一键启动web工程的关键。SpringBoot 嵌入式 Servlet 容器,默认支持的 webServe:Tomcat、Jetty、Undertow,但要在POM文件加入tomcat相关配置。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion> <!--必须要把内嵌的 Tomcat 容器-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

在这里插入图片描述

protected void onRefresh() throws BeansException

/**
     * 创建 WebServer启动Web服务
     */
    @Override
    protected void onRefresh() {
        // 初始化给定应用程序上下文的主题资源
        super.onRefresh();
        try {
            // 创建Web 服务
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

    /**
     * super.onRefresh();
     * Initialize the theme capability.
     */
    @Override
    protected void onRefresh() {
        /**
         * 初始化给定应用程序上下文的主题资源,自动检测一个名为“themeSource”的bean。
         * 如果没有这样的,将使用默认的(空的)ThemeSource。
         */
        this.themeSource = UiApplicationContextUtils.initThemeSource(this);
    }

    /**
     * 创建Web 服务
     */
    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            // 获取web server
            StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            // 获取创建容器的工厂
            ServletWebServerFactory factory = getWebServerFactory();
            createWebServer.tag("factory", factory.getClass().toString());
            /**
             * 获取 tomcat 、Jetty 或 Undertow 容器
             * 从 getWebServer 方法点进去,找到 TomcatServletWebServerFactory 的实现方法,
             * 与之对应的还有 Jetty 和 Undertow。这里配置了基本的连接器、引擎、虚拟站点等配置。
             * 自动配置类 ServletWebServerFactoryAutoConfiguration 导入了 ServletWebServerFactoryConfiguration(配置类),
             * 根据条件装配判断系统中到底导入了哪个 Web 服务器的包,创建出服务器并启动
             * 默认是 web-starter 导入 tomcat 包,容器中就有 TomcatServletWebServerFactory,创建出 Tomcat 服务器并启动
             */
            this.webServer = factory.getWebServer(getSelfInitializer());
            createWebServer.end();
            getBeanFactory().registerSingleton("webServerGracefulShutdown",
                    new WebServerGracefulShutdownLifecycle(this.webServer));
            getBeanFactory().registerSingleton("webServerStartStop",
                    new WebServerStartStopLifecycle(this, this.webServer));
        }
        else if (servletContext != null) {
            try {
                // 启动web server
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }

    /**
     * 获取tomcat 容器
     * 配置了基本的连接器、引擎、虚拟站点等配置
     * @param initializers
     * @return
     */
    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        /**
         * 创建了Tomcat对象,并设置参数
         */
        Tomcat tomcat = new Tomcat();
        // 设置工作忙碌
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        // 初始化tomcat 连接,默认NIO
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        // 配置基本的连接器、引擎、虚拟站点
        tomcat.setConnector(connector);
        // 设置自动部署为false
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        // 准备上下文
        prepareContext(tomcat.getHost(), initializers);
        // 返回TomcatWebServer服务
        return getTomcatWebServer(tomcat);
    }

    /**
     * Create a new {@link TomcatWebServer} instance.
     * @param tomcat the underlying Tomcat server
     * @param autoStart if the server should be started
     * @param shutdown type of shutdown supported by the server
     * @since 2.3.0
     */
    public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
        // 初始化Tomcat
        initialize();
    }

    /**
     * 初始化Tomcat
     * @throws WebServerException
     */
    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();

                Context context = findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                        // Remove service connectors so that protocol binding doesn't
                        // happen when the service is started.
                        removeServiceConnectors();
                    }
                });

                // Start the server to trigger initialization listeners
                this.tomcat.start();

                // We can re-throw failure exception directly in the main thread
                rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
                }
                catch (NamingException ex) {
                    // Naming is not enabled. Continue
                }

                // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                // blocking non-daemon to stop immediate shutdown
                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                stopSilently();
                destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

2.8、afterRefresh

afterReftesh() 刷新后处理,是个一空实现的扩展接口,留着后期扩展如用户自定义容器刷新后的处理逻辑。

在这里插入图片描述

2.9、停止计时并打印启动完毕相关日志

在这里插入图片描述
在这里插入图片描述

2.10、started

started 发布监听应用启动事件。
在这里插入图片描述

void started(ConfigurableApplicationContext context)

/**
     * 发布应用监听启动事件
     * @param context
     */
    void started(ConfigurableApplicationContext context) {
        // listener.started(context) 中交由context.publishEvent()方法处理
        // 实际上是发送了一个ApplicationStartedEvent的事件
        doWithListeners("spring.boot.application.started", (listener) -> listener.started(context));
    }

    /**
     * 发布应用启动事件ApplicationStartedEvent.
     * @param context
     */
    @Override
    public void started(ConfigurableApplicationContext context) {
        context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
        AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
    }

2.11、callRunners

callRunners,执行runner主要是遍历所有的runner获取所有的ApplicationRuner 和CommandLineRunner 来初始化参数,其中callRuner(是一个回调函数)。
在这里插入图片描述

private void callRunners(ApplicationContext context, ApplicationArguments args)

/**
     * 执行runner 初始化参数
     * @param context
     * @param args
     */
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        // 遍历所有runner
        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                /**
                 * 回调函数callRunner 处理 ApplicationRunner
                 */
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                /**
                 * 回调函数callRunner 处理 CommandLineRunner
                 */
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

2.12、running

running 发布上下文完成准备事件,listeners.running() 发布上下文完成准备事件同前面的listeners.started() 方法一样,都是发布了一个running事件,代码也相同。
在这里插入图片描述

void running(ConfigurableApplicationContext context)

/**
     * 发布上下文完成准备事件
     * 与上面的 listeners.started() 方法一样
     * @param context
     */
    void running(ConfigurableApplicationContext context) {
        // listener.started(context) 中交由context.publishEvent()方法处理
        // 实际上是发送了一个ApplicationStartedEvent的事件
        doWithListeners("spring.boot.application.running", (listener) -> listener.running(context));
    }

    /**
     * 发布上下文完成准备事件
     * Called immediately before the run method finishes, when the application context has
     * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
     * {@link ApplicationRunner ApplicationRunners} have been called.
     * @param context the application context.
     * @since 2.0.0
     */
    @Override
    public void running(ConfigurableApplicationContext context) {
        context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
        AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
    }

这也是SpringBoot启动流程两大过程中的第二阶段的启动方法run中最后一个方法了,该方法执行完成后,SpringApplication的run(String… args)方法执行结束,至此Spring Boot的ApplicationContext 启动结束。

四、总结

SpringBoot启动流程总结就是下面两张图片,一个创建SpringApplication实例,一个执行run方法,所有的猫腻都在其中。
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/337327.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于RK3588的嵌入式linux系统开发(一)——开发环境的搭建(SDK解压与本地初始化)

1、拷贝rk3588的linux-sdk压缩包到工作目录&#xff0c;如下所示&#xff1a; 图1 拷贝rk3588的sdk到工作目录2、进入sdk目录进行MD5码的计算&#xff0c;并对比md5sum.txt文件内的值&#xff0c;确保压缩包未被修改。 图2 MD5码计算与匹配3、安装p7zip-full工具&#xff0c;并…

shell正则表达式

文章目录七、正则表达式7.1 什么是正则表达式7.2 为什么使用正则表达式7.3 如何学习正则表达式7.4 如何使用正则表达式7.5 基本正则表达式7.6 扩展正则表达式7.7 正则表达式案例七、正则表达式 7.1 什么是正则表达式 正则表达式是通过一些特殊字符的排列&#xff0c;用以查找…

【Linux 进程间通信】管道和共享内存

1.进程间通信的概念2.匿名管道匿名管道的5个特点管道是一个单向通信的通信信道&#xff1b;匿名管道作用与具有血缘关系的进程&#xff0c;常用于父子进程&#xff1b;管道是一个文件&#xff0c;生命周期随进程&#xff1b;管道自带同步机制、原子性&#xff1b;管道是面向字节…

二叉查找树的应用 —— K模型和KV模型

文章目录前言1. K模型2. KV模型&#x1f351; 构建KV模型的树&#x1f351; 英汉词典&#x1f351; 统计水果出现的次数3. 总结前言 在上一篇文章中&#xff0c;我们进行了二叉查找树的实现&#xff08;文章链接&#xff09;&#xff0c;那么今天主要探讨一下二叉查找树的应用…

阻塞队列、阻塞队列的实现原理、七种阻塞队列分析及源码解读、使用阻 塞队列来实现生产者-消费者模型

文章目录面试回答参考语术七种队列分析及源码解读ArrayBlockingQueue2.1.0 ArrayBlockingQueue分析2.1.1 ArrayBlockingQueue源码解读&#xff1a;LinkedBlockingQueue2.2.0 LinkedBlockingQueue分析2.2.1 LinkedBlockingQueue源码解读2.3 LinkedBlockingQueue 与 ArrayBlockin…

【浅学Redis】Spring Cache的基础使用

用SpringCache操作Redis缓存数据1. Spring Cache是什么2. Spring Cache 常用注释3. Spring Cache 的使用步骤4. 使用Spring Cache操作Redis1. Spring Cache是什么 Spring Cache是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单的加一个注解&#xff0c;…

计算机视觉框架OpenMMLab开源学习(六):语义分割基础

✨写在前面&#xff1a;强烈推荐给大家一个优秀的人工智能学习网站&#xff0c;内容包括人工智能基础、机器学习、深度学习神经网络等&#xff0c;详细介绍各部分概念及实战教程&#xff0c;通俗易懂&#xff0c;非常适合人工智能领域初学者及研究者学习。➡️点击跳转到网站。…

工地安全帽智能识别系统 YOLOv5

工地安全帽智能识别系统通过opencv深度学习技术&#xff0c;实现对现场人员的安全帽反光衣穿戴进行自动实时识别和检测。我们选择当下YOLO最新的卷积神经网络YOLOv5来进行识别检测。6月9日&#xff0c;Ultralytics公司开源了YOLOv5&#xff0c;离上一次YOLOv4发布不到50天。而且…

Allegro172版本线到铜皮不按照设定值避让的原因和解决办法

Allegro172版本线到铜皮不按照设定值避让的原因和解决办法 用Allegro做PCB设计的时候,有时会单独给某块铜皮附上线到铜皮额外再增加一个数值,如下图 在规则的基础上,额外再避让10mil 规则避让line到铜皮10.02mil 额外设置多避让10mil,避让的结果却是30.02mil,正确的是20.…

2023金三银四季跳槽季,啃完这软件测试面试题,跳槽不就稳稳的了

前言 2023年也到来了&#xff0c;接近我们所说的“金三银四”也正在执行了&#xff0c;时间晃眼就过去了&#xff0c;有的人为了2023跳槽早早做足了准备&#xff0c;有的人在临阵磨刀&#xff0c;想必屏幕前的你也想在2023年涨薪吧&#xff0c;那么问题来了&#xff0c;怎么才…

day4——与数组有关的练习

今天是学习java的第四天&#xff0c;主要内容有 给循环起一个标签数组的定义以及数组的初始化 给循环起一个标签 给循环起一个标签&#xff0c;简单的说就是给循环起一个名字&#xff0c;内部的循环可以控制外部的循环&#xff0c;外部的循环可以控制内部的循环&#xff0c;…

第四章:搭建Windows server AD域和树域

由于Windows简单一点&#xff0c;我就先搞Windows了。AD域&#xff1a;视频教程&#xff1a;https://www.bilibili.com/video/BV1f84y1G72x/在创建AD域时要把网卡配置好这是打开网卡界面的命令DNS要改成自己的&#xff0c;因为在创建域的同时也会自动创建DNS打开服务器管理器&a…

LANP架构搭建

安装Apache解压apache安装包&#xff08;httpd-2.4.17.tar.gz&#xff09;到 /usr/src/目录下面tar -zxvf /root/httpd-2.4.17.tar.gz -C /usr/src/安装httpd所需要的依赖包yum -y install zlib* openssl* apr* pcre-devel openssl*进入httpd目录&#xff0c;安装httpd所需要的…

个人ChatGPT账号注册

作者&#xff1a;Bruce.Dgithub&#xff1a;https://github.com/doukoi-BDB文章底部有【技术社群&福利】&#xff0c;不定更新活动、源码&#xff0c;欢迎来撩~~~今日主题&#xff1a;1、ChatGPT 账号注册2、预计阅读 6 分钟&#xff0c;正文2000字。最近啊 一款类似人工智…

服务降级和熔断机制

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;服务降级和熔断机制 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪…

状态机设计中的关键技术

⭐本专栏针对FPGA进行入门学习&#xff0c;从数电中常见的逻辑代数讲起&#xff0c;结合Verilog HDL语言学习与仿真&#xff0c;主要对组合逻辑电路与时序逻辑电路进行分析与设计&#xff0c;对状态机FSM进行剖析与建模。 &#x1f525;文章和代码已归档至【Github仓库&#xf…

IT行业寒冬,干测试从月薪18k降到了15k,“我”的路在何方

今天已经是2.10了&#xff0c;马上就是金3银4了&#xff0c;2023年才开始&#xff0c;是的&#xff0c;正值春天&#xff0c;想到了一首诗词自古逢秋悲寂寥&#xff0c;我言秋日胜春朝。晴空一鹤排云上&#xff0c;便引诗情到碧霄。秋天&#xff0c;意味着收获&#xff0c;也意…

【C语言】“指针类型”与“野指针”

文章目录一、指针是什么❔二、指针和指针类型1.指针-整数2.指针解引用三.野指针1.引起野指针的原因2.如果避免野指针完结一、指针是什么❔ 指针也就是 内存地址 &#xff0c;在计算机上我们访问数据需要通过内存地址来访问&#xff0c;在C语言中&#xff0c;指针变量是用来存放…

如何编写Python程序调用ChatGPT,只需3步

如何编写Python程序调用ChatGPT&#xff0c;只需3步 在ChatGPT官网进行注册&#xff0c;注册成功后就可以对ChatGPT进行提问&#xff0c;ChatGPT的注册流程参考这篇文章——手把手教你注册ChatGPT&#xff0c;亲测可用。 来看看ChatGPT&#xff0c;如何回答”ChatGPT是什么“…

Deepwalk深度游走算法

主要思想 Deepwalk是一种将随机游走和word2vec两种算法相结合的图结构数据的挖掘算法。该算法可以学习网络的隐藏信息&#xff0c;能够将图中的节点表示为一个包含潜在信息的向量&#xff0c; Deepwalk算法 该算法主要分为随机游走和生成表示向量两个部分&#xff0c;首先…