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

SpringBoot启动过程分析——启动过程源码分析,第1张

SpringBoot启动过程分析——启动过程源码分析 项目启动入口

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

public static ConfigurableApplicationContext run(Class primarySource, String... args) {
    return run(new Class[] { primarySource }, args);
}


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

SpringApplication主要的构造函数

在这一步会去加载初始化器和监听器

加载的方式都是通过getSpringFactoriesInstances方法调用loadSpringFactories从"meta-INF/spring.factories"文件读入要加载的类的全路径类型,获取到全路径类名之后,通过如下代码,根据全路径类名通过反射的方式创建相应的bean:

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

private  List createSpringFactoriesInstances(Class type, Class[] parameterTypes,
        ClassLoader classLoader, Object[] args, Set names) {
    List 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容器

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实现的

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 {


default void starting(ConfigurableBootstrapContext bootstrapContext) {
    starting();
}



default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
        ConfigurableEnvironment environment) {
    environmentPrepared(environment);
}



default void contextPrepared(ConfigurableApplicationContext context) {
}


default void contextLoaded(ConfigurableApplicationContext context) {
}


default void started(ConfigurableApplicationContext context) {
}


default void running(ConfigurableApplicationContext context) {
}


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 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过程(下篇)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5137299.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-17
下一篇 2022-11-17

发表评论

登录后才能评论

评论列表(0条)