SpringBoot启动过程分析——启动过程源码分析

项目启动入口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4ko3Qg9-1636299416298)(en-resource://database/1618:1)]

实际执行的内容是通过SpringApplication类的静态方法创建一个ConfigurableApplicationContext,顾名思义,即可配置的对象容器,也就是Springboot中的上下文

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified source using default settings.
 * @param primarySource the primary source to load
 * @param args the application arguments (usually passed from a Java main method)
 * @return the running {@link ApplicationContext}
 * primarySource为Springboot启动类,args为启动参数
 */
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified sources using default settings and user supplied arguments.
 * @param primarySources the primary sources to load
 * @param args the application arguments (usually passed from a Java main method)
 * @return the running {@link ApplicationContext}
 */
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

SpringApplication类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-va8Ur3qx-1636299416304)(en-resource://database/1620:1)]

SpringApplication主要的构造函数

在这一步会去加载初始化器和监听器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cct0pxjh-1636299416307)(en-resource://database/1622:1)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGoUjaOT-1636299416315)(en-resource://database/1621:1)]

加载的方式都是通过getSpringFactoriesInstances方法调用loadSpringFactories从"META-INF/spring.factories"文件读入要加载的类的全路径类型,获取到全路径类名之后,通过如下代码,根据全路径类名通过反射的方式创建相应的bean:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-waMIm6HC-1636299416318)(en-resource://database/1623:1)]

下面这个方法就是通过反射来创建bean实例了:

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 {
            //看到这个forName应该有点熟悉吧,没错,就是通过反射的方式加载实例化bean的
            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;
}

在通过构造函数创建SpringApplication类的实例时,会先加载一部分能够加载的资源

SpringApplication对象的run方法

先附上这个方法的完整代码,通过注释可以知道这个方法的作用就是创建和刷新一个新的ApplicationContext,也就是Springboot的上下文bean容器

/**
 * 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) {
    //1.StopWatch为一个简单地计时器,记录Springboot应用启动的时间
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    //设置java.awt.headless系统属性为true,Headless模式是系统的一种配置模式。
    // 在该模式下,系统缺少了显示设备、键盘或鼠标。但是服务器生成的数据需要提供给显示设备等使用。
    // 因此使用headless模式,一般是在程序开始激活headless模式,告诉程序,现在你要工作在Headless        mode下,依靠系统的计算能力模拟出这些特性来
    configureHeadlessProperty();
    //2.获取监听器集合对象
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //发出开始执行的starting事件
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //3.根据SpringApplicationRunListeners以及参数来准备环境,这一步会发出environmentPrepared事件
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        //打印banner就是启动项目时打印的图案
        Banner printedBanner = printBanner(environment);
        4.根据WebApplicationType创建ApplicationContext容器
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        //5.初始化ApplicationContext,这一步会先后发布contextPrepared和contextLoaded两个事件
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //刷新context
        refreshContext(context);
        //没有逻辑
        afterRefresh(context, applicationArguments);
        //计时结束
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        //发布started事件
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        //发布running事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        //如果失败,会发布failed事件  
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

1.创建计时器开始计时

StopWatch会记录项目开始启动到启动完毕的时间,我们在启动项目的时候日志里面会有一行日志输出启动时间就是通过StopWatch实现的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O6O7reom-1636299416321)(en-resource://database/1624:1)]

2.加载SpringApplicationRunListeners

https://www.codenong.com/cs106018032/
首先第一步是:通过SpringFactoriesLoader 到META-INF/spring.factories查找并加载所有的SpringApplicationRunListeners,通过start()方法通知所有的SpringApplicationRunListener,本质上这是一个事件发布者,他在SpringBoot应用启动的不同阶段会发布不同的事件类型。
SpringApplicationRunListener接口只有一个实现类EventPublishingRunListener,也就是说SpringApplicationRunListeners类的List listeners中只会生成一个EventPublishingRunListener实例。那么SpringApplicationRunListener是如何发布事件类型的呢?首先我们看下SpringApplicationRunListener这个接口。接口每个方法的注释上面都将其调用的时机交代得很清楚
SpringApplicationRunListener监听器SpringBoot应用启动的不同阶段都会有相应的监听通知。通知贯穿了SpringBoot应用启动的完成过程

public interface SpringApplicationRunListener {

/**run方法刚执行时通知
 * Called immediately when the run method has first started. Can be used for very
 * early initialization.
 * @param bootstrapContext the bootstrap context
 */
default void starting(ConfigurableBootstrapContext bootstrapContext) {
    starting();
}


/**Environment准备好,ApplicationContext被创建好之前通知
 * Called once the environment has been prepared, but before the
 * {@link ApplicationContext} has been created.
 * @param bootstrapContext the bootstrap context
 * @param environment the environment
 */
default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
        ConfigurableEnvironment environment) {
    environmentPrepared(environment);
}


/**ApplicationContext被准备好之后,但是sources没有被加载之前通知
 * Called once the {@link ApplicationContext} has been created and prepared, but
 * before sources have been loaded.
 * @param context the application context
 */
default void contextPrepared(ConfigurableApplicationContext context) {
}

/**ApplicationContext被加载好之后,但是没有刷新之前通知
 * Called once the application context has been loaded but before it has been
 * refreshed.
 * @param context the application context
 */
default void contextLoaded(ConfigurableApplicationContext context) {
}

/**
 * The context has been refreshed and the application has started but
 * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
 * ApplicationRunners} have not been called.
 * @param context the application context.
 * @since 2.0.0
 */
default void started(ConfigurableApplicationContext context) {
}

/**run方法结束之前并且所有的application 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
 */
default void running(ConfigurableApplicationContext context) {
}

/**运行application失败
 * Called when a failure occurs when running the application.
 * @param context the application context or {@code null} if a failure occurred before
 * the context was created
 * @param exception the failure
 * @since 2.0.0
 */
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}

3.创建并配置当前应用将要使用的环境

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // 获取创建的环境,如果没有则创建,如果是web环境则创建StandardServletEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //配置Environment:配置profile以及properties
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
            "Environment prefix cannot be set via properties.");
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

Environment简介可以参考:springboot启动流程(三)Environment简介
创建并配置当前应用的环境(Environment),Environment用于描述应用程序当前的运行环境,其抽象了两方面的内容:
配置文件(profile)和属性(properties),我们知道不同的环境(开发环境,测试环境,发布环境)可以使用不同的属性配置,这些属性配置可以从配置文件,环境变量,命令行参数等来源获取。因此,当Environment准备好之后,在整个应用的任何时候,都可以获取这些属性。
配置文件加载过程
所以,这一步的做的事情主要有三件:

  1. 获取创建的环境(Environment),如果没有则创建,如果是web环境则创建StandardServletEnvironment,如果不是的话则创建StandardEnvironment。
  2. 配置环境(Environment):主要是配置profile和属性properties
  3. 调用SpringApplicationRunListener的environmentPrepared方法,通知事件监听者:应用环境(Environment)已经准备好了。

4.根据WebApplicationType创建ApplicationContext容器

最终的创建代码是一段lambda表达式,根据对应的webApplicationType创建ApplicationContextFactory

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    try {
        switch (webApplicationType) {
        case SERVLET:
            return new AnnotationConfigServletWebServerApplicationContext();
        case REACTIVE:
            return new AnnotationConfigReactiveWebServerApplicationContext();
        default:
            return new AnnotationConfigApplicationContext();
        }
    }
    catch (Exception ex) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                + "you may need a custom ApplicationContextFactory", ex);
    }
};

5.初始化ApplicationContext

前面个步骤已经创建好了与本应用环境相匹配的ApplicationContext实例,那么接下来就是对ApplicationContext进行初始化了。这一步也是比较核心的一步。首先让我们来看看实现逻辑的相关代码:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    //1. 将准备好的Environment设置给ApplicationContext
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    //2. 遍历调用所有的ApplicationContextInitializer的  initialize()  方法来对已经创建好的 ApplicationContext 进行进一步的处理。
    applyInitializers(context);
    //3. 调用SpringApplicationRunListeners的 contextPrepared()  方法,通知所有的监听者,ApplicationContext已经准备完毕
    listeners.contextPrepared(context);
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    //4. 将applicationArguments实例注入到IOC容器
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        //5. 将printedBanner实例注入到IOC容器
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    //6. 加载资源,这里的资源一般是启动类xxxApplication
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //7. 将所有的bean加载到容器中
    load(context, sources.toArray(new Object[0]));
    //8.通知所有的监听者:ApplicationContext已经装载完毕
    listeners.contextLoaded(context);
}

以上就是初始化ApplicationContext的主要逻辑,主要有如下逻辑:

  1. 将准备好的Environment设置给ApplicationContext
  2. 遍历调用所有的ApplicationContextInitializer的 initialize() 方法来对已经创建好的 ApplicationContext 进行进一步的处理
  3. 调用SpringApplicationRunListeners的 contextPrepared() 方法,通知所有的监听者,ApplicationContext已经准备完毕
  4. 将applicationArguments实例注入到IOC容器。
  5. 将printedBanner实例注入到IOC容器,这个就是之前生成的Banner的实例。
  6. 加载资源,这里的资源一般是启动类xxxApplication
  7. 将所有的bean加载到容器中
  8. 通知所有的监听者:ApplicationContext已经装载完毕。

更详细的代码分析可参考:springboot启动流程(六)ioc容器刷新前prepareContext

6.调用ApplicationContext的refresh() 方法

上下文这个步骤将会解析xml配置以及java配置,从而把Bean的配置解析成为BeanDefinition,将从而把Bean的配置解析成为BeanDefinition加载到上下文容器。这里的 SpringApplication的 refresh方法最终还是调用到AbstractApplicationContext的refresh方法。
AbstractApplicationContext的refresh方法:

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 刷新前准备,设置flag、时间,初始化properties等
        prepareRefresh();

        // 获取ApplicationContext中组合的BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 设置类加载器,添加后置处理器等准备
        prepareBeanFactory(beanFactory);

        try {
            // 供子类实现的后置处理
            postProcessBeanFactory(beanFactory);

            // 调用Bean工厂的后置处理器,实际的bean初始化和加载都在这一步完成
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册Bean的后置处理器
            registerBeanPostProcessors(beanFactory);

            // 初始化消息源
            initMessageSource();

            // 初始化事件广播
            initApplicationEventMulticaster();

            // 供之类实现的,初始化特殊的Bean
            onRefresh();

            // 注册监听器
            registerListeners();

            // 实例化所有的(non-lazy-init)单例Bean
            finishBeanFactoryInitialization(beanFactory);

            // 发布刷新完毕事件
            finishRefresh();
        }

        catch (BeansException ex) {
            // 
        } finally {        
            // 
        }
    }
}

最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。ioc容器的refresh过程先做一个小结。我们知道了上下文和Bean容器是继承关系又是组合关系。refreshContext的核心就是为了加载BeanDefinition,而加载BeanDefinition将从main方法所在的主类开始,主类作为一个配置类将由ConfigurationClassParser解析器来完成解析的职责
这一步骤细节参考:springboot启动流程(七)ioc容器refresh过程(上篇)springboot启动流程(八)ioc容器refresh过程(下篇)

猜你喜欢

转载自blog.csdn.net/crazyjack1/article/details/121199589