SpringBoot源码之:启动流程

135 阅读14分钟

1. SpringApplication

1.1. 成员变量

/**
 * 本类可以用于通过Java的main()方法来启动一个Spring应用;默认情况下本类会执行如下操作来启动应用:
 * 1. 创建一个合适的ApplicationContext实例(会根据类路径下是否存在特定的类来确定目标ApplicationContext的类型)
 * 2. 注册一个CommandLinePropertySource属性源,将命令行参数封装成Spring属性
 * 3. 刷新ApplicationContext并加载所有的单例Bean
 * 4. 调用CommandLineRunner/ApplicationRunner组件
 * 
 * 我们一般是直接通过本类的静态run()方法来启动应用的
 * 另一方面,我们也可以手动创建一个SpringApplication实例,并调用其set方法进行一些自定义的配置,最后再调用其run()方法
 * 
 * SpringApplication可以从多种不同的源(即比如配置类)中读取Bean的定义信息
 * 一般来说,推荐只使用一个配置类;如果还需要读取其它源,则可以调用setSources()方法来指定额外的字符串类型的源
 * 这些字符串类型的源可以是:配置类的全限定类名、xml配置文件的路径、groovy脚本、要扫描的基础包名
 * 
 * 另外,spring.main.xxx属性的值将会自动赋给本类实例的相应字段,以方便我们动态配置SpringApplication实例
 * 比如,可以通过spring.main.sources属性来指定额外的源,通过spring.main.banner-mode属性来指定Banner的打印策略
 */
public class SpringApplication {

    /** Banner的默认路径为"banner.txt" */
    public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;

    /** Banner路径对应的属性名称为"spring.banner.location" */
    public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;

    /** 本机的无显示设备状态对应的系统属性名称;如果是无显示设备状态,则Java中的与AWT相关的组件将不会被初始化 */
    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

    /** Spring应用的关闭钩子;该钩子会等到所有Spring容器都关闭完成后,才允许JVM开始关闭;注意该属性是静态的 */
    static final SpringApplicationShutdownHook shutdownHook = new SpringApplicationShutdownHook();

    /** Spring容器的主配置类 */
    private Set<Class<?>> primarySources;

    /** 额外的配置源(可以是配置类的全限定类名、xml配置文件的路径等) */
    private Set<String> sources = new LinkedHashSet<>();

    /** main()方法所在的主程序类;主程序类主要用于获取Log实例和版本信息 */
    private Class<?> mainApplicationClass;

    /** Banner的打印策略;默认打印到控制台 */
    private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

    /** 是否记录启动时的日志;默认为true */
    private boolean logStartupInfo = true;

    /** 是否将命令行参数封装成一个属性源并将其添加到Environment的属性源集合中;默认为true */
    private boolean addCommandLineProperties = true;

    /** 是否给Environment和BeanFactory添加一个与类型转换相关的组件;默认为true */
    private boolean addConversionService = true;

    /** 备选的Banner */
    private Banner banner;
    
    // 当未通过spring.banner.location等属性指定Banner路径、且类路径下不存在banner.txt等文件时,将会打印备选的Banner
    // 如果未指定备选的Banner,则使用默认的SpringBootBanner实现(我们最熟悉的那个Banner),它负责打印Spring的logo

    /** 用户自定义的资源加载器 */
    private ResourceLoader resourceLoader;

    /** 用户自定义的Bean名称生成器 */
    private BeanNameGenerator beanNameGenerator;

    /** 用户自定义的环境信息 */
    private ConfigurableEnvironment environment;

    /** web应用的类型;如果用户未指定,则本组件会自己推断 */
    private WebApplicationType webApplicationType;

    /** 本机是否为无显示设备状态(不会覆盖已有的java.awt.headless系统属性值) */
    private boolean headless = true;

    /** 是否将本SpringApplication创建的Spring容器注册到关闭钩子中 */
    private boolean registerShutdownHook = true;

    /** Spring容器的初始化器 */
    private List<ApplicationContextInitializer<?>> initializers;

    /** 事件监听器;这些监听器在Spring容器创建前也能发挥作用 */
    private List<ApplicationListener<?>> listeners;

    /** 用于存放一些默认的属性;这些属性将会被封装成一个属性源并添加到Environment的属性源集合中 */
    private Map<String, Object> defaultProperties;

    /** BootstrapRegistry的初始化器 */
    private List<BootstrapRegistryInitializer> bootstrapRegistryInitializers;

    /** 需要额外激活的Profile的名称(会和spring.profiles.active属性值一同生效) */
    private Set<String> additionalProfiles = Collections.emptySet();

    /** 是否允许重写Spring容器中的Bean定义信息 */
    private boolean allowBeanDefinitionOverriding;

    /** 用户是否自己通过setEnvironment()方法来指定了环境信息 */
    private boolean isCustomEnvironment = false;

    /** Spring容器中的Bean是否默认懒加载 */
    private boolean lazyInitialization = false;

    /** 系统属性名称的前缀;在通过系统属性获取配置属性(即ConfigurationProperty)时,属性的名称需要拼接上该前缀 */
    private String environmentPrefix;
    
    // environmentPrefix字段使得我们可以通过System.getProperties()集合来为不同的Spring容器进行相应的配置
    // 比如,假设我们希望通过系统属性来配置管理员密码,属性名称为"admin.pwd",并且不同的Spring容器要用不同的管理员密码
    // 那么可以添加ctx1.admin.pwd、ctx2.admin.pwd等系统属性;哪个Spring容器想要xxx.admin.pwd的配置,就将其前缀指定为"xxx"
    // 而Spring容器在获取自己的管理员密码时,只需通过"admin.pwd"属性名来获取,而不用自己拼接上前缀
    // 因此,environmentPrefix字段的作用类似于命名空间,使得不同的Spring容器的配置可以存放在同一个集合,却不会相互影响(隔离性)

    /** Spring容器工厂,会根据web应用的类型来创建相应的Spring容器 */
    private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;

    /** 这个组件主要负责记录运行信息;ApplicationStartup.DEFAULT是个空实现,不用管 */
    private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
}

1.2. 构造方法

public class SpringApplication {
    
    /**
     * 静态run()方法;底层是在调用另一个静态run()方法
     */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    /**
     * 静态run()方法;底层是在创建一个SpringApplication实例,并调用其run()方法
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

    /**
     * 构造方法;底层是在调用另一个构造方法
     */
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

    /**
     * 构造方法;需指定资源加载器(可能为null)和主配置类
     * 在构造出SpringApplication实例后,用户可以先对其进行一些自定义的配置,最后再调用其run()方法来获取Spring容器
     */
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        Assert.notNull(primarySources, "PrimarySources must not be null");

        // 先把资源加载器和主配置类保存下来
        this.resourceLoader = resourceLoader;
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        
        // 根据类路径下是否有特定的类来推断web应用类型;假如类路径下有与Servlet相关的类,则类型为WebApplicationType.SERVLET
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        
        // 获取所有在spring.factories文件中配置的BootstrapRegistryInitializer组件,并根据order值进行排序
        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        
        // 获取所有在spring.factories文件中配置的ApplicationContextInitializer组件,并根据order值进行排序
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

        // 获取所有在spring.factories文件中配置的ApplicationListener组件,并根据order值进行排序
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        
        // 推断主程序类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
}

1.3. 构造方法(补充)

public class SpringApplication {

    /**
     * 获取spring.factories文件中的所有type类型的组件;这里会通过指定的构造参数来实例化这些组件,然后根据order值进行排序
     */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        
        // 获取到spring.factories文件中的所有type类型的组件的全限定类名
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        
        // 根据指定的构造参数来实例化这些类,并进行排序;最后返回这些组件
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    
    /**
     * 获取spring.factories文件中的所有BootstrapRegistryInitializer组件
     */
    private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {

        // 先创建一个List集合,用于存放实例化的BootstrapRegistryInitializer组件
        ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();

        // 先获取spring.factories文件中的Bootstrapper组件,并根据order值进行排序
        // 然后将这些组件适配成BootstrapRegistryInitializer类型,然后添加到List集合中
        // 注意,Bootstrapper接口已被废弃,这么做只是为了向下兼容,因此了解即可
        getSpringFactoriesInstances(Bootstrapper.class).stream()
                .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
                .forEach(initializers::add);

        // 继续获取BootstrapRegistryInitializer组件,并将其添加到List集合中,然后返回该集合
        initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        return initializers;
    }

    /**
     * 推断主程序类
     */
    private Class<?> deduceMainApplicationClass() {
        try {
            // 获取到虚拟机栈中的方法调用信息
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();

            // 从上往下依次判断每一个栈帧信息;如果该栈帧对应的方法名称是"main",则返回该栈帧对应的类
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
}

1.4. run()

public class SpringApplication {
    
    /**
     * SpringApplication类中的核心方法;本方法负责创建并刷新Spring容器,并返回该Spring容器
     * 这里的args参数一般是主程序的main()方法接收到的args参数
     */
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        // 先创建一个DefaultBootstrapContext实例,并调用this.bootstrapRegistryInitializers中的初始化器对其进行初始化
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        
        // Spring容器,初始化为null
        ConfigurableApplicationContext context = null;
        
        // 如果System.getProperty("java.awt.headless")为null,则将该属性的值指定为this.headless
        configureHeadlessProperty();
        
        // 从spring.factories文件中获取到所有SpringApplicationRunListener组件
        // 然后将这些监听器封装到一个SpringApplicationRunListeners实例中,方便统一调用
        SpringApplicationRunListeners listeners = getRunListeners(args);
        
        // 调用所有监听器的starting()方法
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        
        try {
            // 将接收到的参数封装成ApplicationArguments形式
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            
            // 对Environment实例进行一些准备工作,并获取到已准备完成的Environment实例
            // 准备完成之后,会调用所有监听器的environmentPrepared()方法
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            
            // 如果System.getProperty("spring.beaninfo.ignore")为null,则将Environment中的同名属性的值(默认为true)赋给该属性
            configureIgnoreBeanInfo(environment);
            
            // 打印Banner
            Banner printedBanner = printBanner(environment);
            
            // 创建Spring容器,并将this.applicationStartup设置给Spring容器
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            
            // 对Spring容器进行一些准备工作,并加载所有配置源(包括主配置类和其它额外的源)
            // 准备完成之后,会调用所有监听器的contextPrepared()方法;配置源加载完成后,会调用所有监听器的contextLoaded()方法
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            
            // 刷新Spring容器;如果是web应用,则该Spring容器在刷新时还会启动一个web服务器
            refreshContext(context);
            
            // Spring容器刷新完成后的回调方法,默认为空实现
            afterRefresh(context, applicationArguments);

            // 停止计时,并记录启动日志
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            
            // 调用所有监听器的started()方法
            listeners.started(context);
            
            // 调用Spring容器中的ApplicationRunner/CommandLineRunner组件
            callRunners(context, applicationArguments);
        
        } catch (Throwable ex) {
            // 如果期间出现了异常,则调用所有监听器的failed()方法,然后抛出运行时异常
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        // 调用所有监听器的running()方法;注意,如果期间抛出异常,则不会调用监听器的failed()方法
        try {
            listeners.running(context);
        } catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        
        // 返回Spring容器
        return context;
    }
}

1.5. run()(补充)

public class SpringApplication {
    
    /**
     * 创建一个DefaultBootstrapContext实例并对其进行初始化
     */
    private DefaultBootstrapContext createBootstrapContext() {
        DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
        this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
        return bootstrapContext;
    }

    /**
     * 配置java.awt.headless系统属性;如果未指定该属性,则将this.headless的值赋给该属性
     */
    private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }

    /**
     * 获取所有SpringApplicationRunListener组件,并将其封装成SpringApplicationRunListeners类型
     * 注意,这里在实例化SpringApplicationRunListener时,会将this和args作为构造参数传给监听器的构造方法
     */
    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
                this.applicationStartup);
    }

    /**
     * 配置spring.beaninfo.ignore系统属性;如果未指定该属性,则将环境中的同名属性值(默认为true)赋给该属性
     */
    private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
            Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
            System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
        }
    }
}

1.6. prepareEnvironment()

public class SpringApplication {

    /**
     * 对Environment实例进行一些准备工作
     */
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        
        // 获取到用户设置的Environment实例;如果用户没有设置,则自动根据web应用类型来创建相应的Environment实例
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        
        // 对Environment实例进行配置;这里主要会添加类型转换服务组件和一些额外的属性源
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        
        // 将Environment底层的属性源集合封装成一个名称为"configurationProperties"的属性源,并将该属性源添加到属性源集合的开头
        ConfigurationPropertySources.attach(environment);
        
        // 调用监听器的environmentPrepared()方法
        listeners.environmentPrepared(bootstrapContext, environment);
        
        // 将默认属性源移到属性源集合的末尾(如果有的话)
        DefaultPropertiesPropertySource.moveToEnd(environment);
        
        // 确保环境中不存在spring.main.environment-prefix属性,然后将spring.main.xxx属性对应的值赋给this的相应字段
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        bindToSpringApplication(environment);
        
        // 如果该Environment实例不是用户传入的,则将该Environment实例转成符合要求的类型
        // 个人觉得这一步有点多余,因为内部创建的Environment实例本身就和deduceEnvironmentClass()类型匹配;至少目前如此
        // 当然,不排除之后会修改getOrCreateEnvironment()/deduceEnvironmentClass()方法的逻辑的可能性
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
        }
        
        // 如果该Environment底层的属性源集合发生了变动(即指针指向了一个新的集合)
        // 则重新创建一个ConfigurationPropertySourcesPropertySource类型的属性源,并将其放到属性源集合的开头
        ConfigurationPropertySources.attach(environment);
        
        return environment;
    }

    /**
     * 获取或创建Environment实例
     * 需要注意的是,如果是创建Environment实例,那么创建出来的实例并不会赋给this.environment字段
     * 
     * ApplicationEnvironment等类的doGetActiveProfilesProperty()/doGetDefaultProfilesProperty()方法固定返回null
     * 也就是说,除非手动调用setActiveProfiles()/setDefaultProfiles()方法设置了一个与默认值不同的值
     * 否则getActiveProfiles()/getDefaultProfiles()方法会固定返回默认值
     */
    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
            case SERVLET: return new ApplicationServletEnvironment();
            case REACTIVE: return new ApplicationReactiveWebEnvironment();
            default: return new ApplicationEnvironment();
        }
    }

    /**
     * 将spring.main.xxx属性与this的相应字段进行绑定
     */
    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception ex) {
            throw new IllegalStateException("Cannot bind to SpringApplication", ex);
        }
    }
}

1.7. configureEnvironment()

public class SpringApplication {
    
    /**
     * 对Environment实例进行配置
     * 默认会配置属性源和Profile信息,并且在必要时添加类型转换服务组件
     * 子类可以根据需要重写该方法来自定义配置Environment实例
     */
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            environment.setConversionService(new ApplicationConversionService());
        }
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

    /**
     * 配置属性源
     * 可以添加属性源、删除属性源或对已有的属性源进行重排序
     * 子类也可以重写该方法来自定义配置属性源
     */
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        
        // 获取到Environment实例中的属性源集合
        MutablePropertySources sources = environment.getPropertySources();
        
        // 如果用户自定义了一些默认属性,则将其封装成一个默认属性源(名称为"defaultProperties")并添加到属性源集合中
        // 如果属性源集合中已经有默认属性源了,则会将用户自定义的默认属性添加到这个默认属性源中
        if (!CollectionUtils.isEmpty(this.defaultProperties)) {
            DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
        }
        
        // 必要时,将命令行参数也封装成一个属性源(名称为"commandLineArgs")并添加到属性源集合中
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            
            // 如果属性源集合中已经存在该名称的属性源了,说明这里的args不是命令行参数,已存在的数据源中的数据才是真正的命令行参数
            // 此时需要把args封装成一个名称为"springApplicationCommandLineArgs"的属性源,并与已有的属性源组合成一个新的属性源
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource compo = new CompositePropertySource(name);
                compo.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                compo.addPropertySource(source);
                sources.replace(name, compo);
            
            // 否则,添加一个名称为"commandLineArgs"的属性源
            } else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

    /**
     * 配置Profile信息
     * 可以指定要激活哪些Profile(或者哪些Profile默认激活)
     * 之后的流程中还会根据spring.profiles.active属性来激活其它Profile
     * 子类可以重写该方法来自定义配置Profile信息
     */
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    }
}

1.8. 创建和刷新Spring容器

public class SpringApplication {
    
    /**
     * 创建Spring容器
     * 默认通过this.applicationContextFactory来创建
     * 子类可以重写该方法来返回自定义的Spring容器
     */
    protected ConfigurableApplicationContext createApplicationContext() {
        return this.applicationContextFactory.create(this.webApplicationType);
    }

    /**
     * 对Spring容器进行一些准备工作
     */
    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        
        // 设置环境信息
        context.setEnvironment(environment);
        
        // 进行一些后置处理,比如注册Bean名称生成器组件、设置资源加载器和类型转换服务
        // 子类也可以重写该方法来进行自定义的后置处理
        postProcessApplicationContext(context);
        
        // 通过this.initializers中的初始化器(会先根据order值排序)来初始化Spring容器,并调用监听器的contextPrepared()方法
        applyInitializers(context);
        listeners.contextPrepared(context);
        
        // DefaultBootstrapContext实例的任务已经完成了,因此关闭掉该容器,并记录启动日志
        bootstrapContext.close(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        
        // 注册SpringBoot中特有的一些Bean
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        
        // 设置是否允许Bean定义信息覆盖,以及是否需要懒加载Bean
        // LazyInitializationBeanFactoryPostProcessor组件会将所有未明确指定是否懒加载的Bean定义信息都指定为懒加载
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        
        // 加载配置源(即解析配置类和配置文件、进行包扫描等),然后调用监听器的contextLoaded()方法
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

    /**
     * 对Spring容器进行后置处理
     */
    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.beanNameGenerator != null) {
            context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                    this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
        if (this.addConversionService) {
            context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
        }
    }

    /**
     * 刷新Spring容器,并在必要时将其注册到关闭钩子中
     */
    private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }
        refresh(context);
    }
}

1.9. callRunners()

public class SpringApplication {

    /**
     * 调用Spring容器中的ApplicationRunner和CommandLineRunner组件
     * 在调用前,会先根据order值进行排序;并且如果其中一个组件执行失败,则直接抛运行时异常
     */
    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);
        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

    private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args);
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
        }
    }

    private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args.getSourceArgs());
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
        }
    }
}

2. 启动流程小结

SpringBoot应用的启动流程分成两个阶段:创建SpringApplication实例、调用该实例的run()方法

在创建SpringApplication实例时,会读取spring.factories文件中的如下类型的组件,并将它们保存下来:

  1. BootstrapperBootstrapRegistryInitializer
  2. ApplicationContextInitializer
  3. ApplicationListener

SpringApplication实例的run()方法的流程为:

  1. 创建一个DefaultBootstrapContext实例,并调用初始化器对其进行初始化
  2. 获取spring.factories文件中的SpringApplicationRunListener类型的组件(该组件会监听run()方法的各个阶段)
  3. 对环境进行一些准备工作:添加默认属性源和命令行属性源,添加对@ConfigurationProperty注解的支持,进行属性绑定
  4. 根据具体情况来创建相应类型的Spring容器
  5. 对Spring容器进行一些准备工作:设置必要的信息(如环境信息),调用初始化器对其进行初始化,加载主配置类和额外的配置源
  6. 刷新Spring容器,并在必要时将其注册到关闭钩子中;Spring容器在刷新时可能还会启动web容器
  7. 回调ApplicationRunner/CommandLineRunner组件

SpringApplication总结:

  1. SpringApplication的主要作用是创建并刷新Spring容器
  2. SpringApplication允许我们对Spring容器进行自定义的配置
  3. SpringApplication提供了很多回调机制和钩子方法,极大地提高了可扩展性