SpringBoot启动探究(三)

前两个小节,我们分别了解了SpringBootApplication注解和自动配置的加载过程,这一小节就可以正式地探究SpringBoot的启动原理了。

从run方法开始

通常,我们运行SpringBoot项目启动类的main函数,就可以顺利的启动整个SpringBoot项目了。在我们等待SpringBoot项目启动的过程中,除了对着console打印区发呆,有没有想过这段时间SpringBoot在做什么?

让我们从SpringApplication.run方法开始,追踪一下启动过程中SpringBoot具体做了哪些事吧。

首先进入run方法:

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

发现run方法创建了一个SpringApplication实例然后又调用的这个实例的run方法,在下面的构造方法中主要是给SpringApplication实例赋一些初始值:

// resourceLoader是null,只有第二个参数是我们项目的启动类
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

我们接着看SpringApplication实例的run方法,方法比较长,主要关注以下几句:

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    // 配置应用的上下文
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    // 创建了listeners
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 应用配置环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 将listeners等重要组件和上下文关联
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 自动化配置的关键
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }

        listeners.started(context, timeTakenToStartup);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var12) {
        this.handleRunFailure(context, var12, listeners);
        throw new IllegalStateException(var12);
    }

    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
        return context;
    } catch (Throwable var11) {
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var11);
    }
}

在梳理run方法的具体细节之前,我们先关注一下启动过程中比较核心的内容:ConfigurableApplicationContext(配置容器上下文),SpringApplicationRunListeners(监听器)和ConfigurableEnvironment(配置环境)

ConfigurableApplicationContext

它是run方法的返回值,它继承了ApplicationContext(所有容器的基类),内部有一些静态常量和方法:

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
    // ...
}

它是可配置的 ApplicationContext ,用户可以根据自己的需要配置应用上下文,在SpringBoot启动时,

它的主要作用是配置一些bean name名称,设置容器id,设置环境变量,设置bean factory的后置处理器,设置监听器,设置类加载器等。

SpringApplicationRunListeners

SpringApplicationRunListener 接口的作用主要就是在Spring Boot 启动初始化的过程中可以通过SpringApplicationRunListener接口回调来让用户在启动的各个流程中可以加入自己的逻辑

package org.springframework.boot;
public interface SpringApplicationRunListener {

    // 1.开始启动:在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
    void starting();
    // 2.Environment构建完成:当environment构建完成,ApplicationContext创建之前,该方法被调用
    void environmentPrepared(ConfigurableEnvironment environment);
    // 3.ApplicationContext构建完成:当ApplicationContext构建完成时,该方法被调用
    void contextPrepared(ConfigurableApplicationContext context);
    // 4.ApplicationContext完成加载:在ApplicationContext完成加载,但没有被刷新前,该方法被调用
    void contextLoaded(ConfigurableApplicationContext context);
    // 5.ApplicationContext完成刷新并启动:在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
    void started(ConfigurableApplicationContext context);
    // 6.启动完成:在run()方法执行完成前该方法被调用
    void running(ConfigurableApplicationContext context);
    // 7.启动失败:当应用运行出错时该方法被调用
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

SpringApplicationRunListener的接口被启动流程中的事件点的实现原理?

EventPublishingRunListener类实现了SpringApplicationRunListener,它具有广播事件的功能。

它使用了Spring广播器SimpleApplicationEventMulticaster:

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    Iterator var3 = application.getListeners().iterator();

    while(var3.hasNext()) {
        ApplicationListener listener = (ApplicationListener)var3.next();
        this.initialMulticaster.addApplicationListener(listener);
    }

}

ConfigurableEnvironment

Environment是spring中配置文件数据的载体,对外暴露使用的配置文件名称,如:profiles.active,这样我们就可以知道在使用哪个配置文件的信息了。

ConfigurableEnvironment继承于Environment,并且ConfigurableEnvironment还继承了ConfigurablePropertyResolver,而ConfigurablePropertyResolver继承于PropertyResolver。

对于PropertyResolver上一章已经说了,它是对于键值属性PropertySource对外数据的统一处理。实现它来获取PropertySource的数据。

所以ConfigurableEnvironment不仅提供了配置文件解析的数据,以及配置文件名称,还提供了PropertySource数据。其实配置文件的解析出来的数据,也是封装成了PropertySource放在ConfigurableEnvironment中。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    
    //设置活动的配置文件
	void setActiveProfiles(String... profiles);
 
    // 增加活动的配置文件
	void addActiveProfile(String profile);
 
    // 设置默认的配置文件
	void setDefaultProfiles(String... profiles);
 
    // 获取PropertySource键值组合的集合
    // 该方法返回一个可编辑的PropertySources,
    // 如果有在启动阶段自定义环境的PropertySources的需求,就可以通过该方法设置
	MutablePropertySources getPropertySources();
 
    // 系统环境变量
	Map getSystemEnvironment();
 
    // 系统配置
	Map getSystemProperties();
 
    // 合并
	void merge(ConfigurableEnvironment parent);
 
}

ConfigurableEnvironment只是接口,它的具体实现是AbstractEnvironment类。

ConfigurableEnvironment是spring中非常重要的角色,可以通过它获得activeProfiles,来判断我们所使用的环境是dev还是test或者prod等等。还可以根据getProperty拿到配置中的信息并且提供类型转换功能,以及获取系统环境变量等信息:

// run()方法中的加载配置环境的代码:
// ConfigurableEnvironment environment = 
// 			this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 根据不同环境获取不同的Enviroment 
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    // 填充启动类参数到enviroment 对象
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    // 更新参数
    ConfigurationPropertySources.attach((Environment)environment);
    // 发布事件
    listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
    DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
    Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    // 绑定到主类
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
        environment = environmentConverter.convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }

    // 将现有参数有封装成proertySources
    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

run启动流程

在了解上面的关键组件后,启动流程就比较清晰了,其实整个启动过程可以分为两个步骤:

  • new SpringBootApplication()
  • SpringBootApplication.run()

其中run()方法就是配置ConfigurableApplicationContext的过程,将重要的listener,environment和content关联起来并完成自动化配置,在此过程中重要的事件被发布出去,从而回调listener的对应接口:

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    // 1. 创建了监听器isteners并启动监听
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 2.加载配置环境ConfigurableEnvironment
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 3.将listeners,environment等重要组件和要返回的
        // ConfigurableApplicationContext上下文关联
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 4.refresh后完成自动化配置
        this.refreshContext(context);
        // 给实现类留的钩子,这里是一个空方法
        this.afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }

        listeners.started(context, timeTakenToStartup);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var12) {
        this.handleRunFailure(context, var12, listeners);
        throw new IllegalStateException(var12);
    }

    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
        // 5.返回配置好的ConfigurableApplicationContext
        return context;
    } catch (Throwable var11) {
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var11);
    }
}

其中两条语句需要关注:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
  //绑定环境
   context.setEnvironment(environment);
  //如果application有设置beanNameGenerator、resourceLoader就将其注入到上下文中,并将转换工具也注入到上下文中
  postProcessApplicationContext(context);
  //调用初始化的切面
   applyInitializers(context);
  //发布ApplicationContextInitializedEvent事件
   listeners.contextPrepared(context);
  //日志
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
     //如果bean名相同的话是否允许覆盖,默认为false,相同会抛出异常
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
  // 这里获取到的是BootstrapImportSelectorConfiguration这个class,而不是自己写的启动来,这个class是在之前注册的BootstrapApplicationListener的监听方法中注入的
   Set sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
  //加载sources 到上下文中
   load(context, sources.toArray(new Object[0]));
  //发布ApplicationPreparedEvent事件
   listeners.contextLoaded(context);
} 
  

this.refreshContext(context)的实现在AbstractApplicationContext类里:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //记录启动时间、状态,web容器初始化其property,复制listener
        prepareRefresh();
        //这里返回的是context的BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //beanFactory注入一些标准组件,例如ApplicationContextAwareProcessor,ClassLoader等
        prepareBeanFactory(beanFactory);
        try {
            //给实现类留的一个钩子,例如注入BeanPostProcessors,这里是个空方法
            postProcessBeanFactory(beanFactory);

            // 调用切面方法
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册切面bean
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // bean工厂注册一个key为applicationEventMulticaster的广播器
            initApplicationEventMulticaster();

            // 给实现类留的一钩子,可以执行其他refresh的工作,这里是个空方法
            onRefresh();

            // 将listener注册到广播器中
            registerListeners();

            // 实例化未实例化的bean
            finishBeanFactoryInitialization(beanFactory);

            // 清理缓存,注入DefaultLifecycleProcessor,发布ContextRefreshedEvent
            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.
            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();
        }
    }
}

下面的图片对启动流程做了一个总结:

SpringBoot启动探究(三)_第1张图片

你可能感兴趣的:(SpringBoot,spring,boot,java,spring)