SpringBoot启动时都做了哪些事(二)?

简介: SpringBoot启动时都做了哪些事(二)?

接上一篇SpringBoot启动时都做了哪些事(一)后,我们继续分析SpringBoot启动过程流程。

核心流程梳理:

  • 实例化监控StopWatch用于记录起止时间
  • 设置java.awt.headless属性,默认为true。Headless模式是系统的一种配置模式。在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式。
  • 获取SpringApplicationRunListener,触发listeners.starting();广播ApplicationStartingEvent事件
  • 获取DefaultApplicationArguments

创建并配置环境,广播ApplicationEnvironmentPreparedEvent

创建environment,实例化过程中会放入四个propertySource:servletContextInitParams、servletConfigInitParams、systemEnvironment、systemProperties

configureEnvironment

获取到一个ConversionService 赋予environment

尝试添加defaultProperties、springApplicationCommandLineArgs对应的propertySource,本文这里为空不添加

将SpringApplication实例的Set additionalProfiles集合与spring.profiles.active信息合并并赋予environment的Set activeProfiles集合

添加configurationProperties对应的ConfigurationPropertySourcesPropertySource

listeners.environmentPrepared(environment);触发每个监听器的环境准备方法,本文这里是广播ApplicationEnvironmentPreparedEvent事件,增加三个PropertySource包括OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}

将环境与SpringApplication绑定起来

设置spring.beaninfo.ignore属性,默认为true


打印banner


创建应用上下文实例AnnotationConfigServletWebServerApplicationContext,这里会同步创建AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner并注册一系列"AnnotationConfigProcessor"。其父类GenericApplicationContext的构造方法中会触发DefaultListableBeanFactory的实例化流程。

获取SpringBootExceptionReporter


prepareContext


为应用上下文绑定环境实例

尝试为context设置ResourceLoader和ClassLoader,并为BeanFactory进行设置ConversionService

触发每一个ApplicationContextInitializer的initialize方法

listeners.contextPrepared(context);广播ApplicationContextInitializedEvent事件

注册单例springApplicationArguments—DefaultApplicationArguments

注册单例springBootBanner

设置是否允许BeanDefinition覆盖,默认是false

加载主启动应用类,注册启动类的BeanDefinition到容器DefaultListableBeanFactory

listeners.contextLoaded(context);触发EventPublishingRunListener的contextLoaded方法,广播ApplicationPreparedEvent事件refreshContext并注册销毁钩子


afterRefresh默认是空方法


listeners.started(context);触发ApplicationStartedEvent事件广播


触发ApplicationRunner和CommandLineRunner run方法执行


listeners.running(context);广播ApplicationReadyEvent事件

SpringApplication的run方法

public ConfigurableApplicationContext run(String... args) {
  //实例化监控
  StopWatch stopWatch = new StopWatch();
  // 设置开始时间startTimeNanos = System.nanoTime();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  // 设置java.awt.headless属性,默认为true
  configureHeadlessProperty();
  // 本质是获取SpringApplicationRunListener,触发其starting方法
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //广播ApplicationStartingEvent事件
  listeners.starting();
  try {
  //应用的外部参数,本文这里没有指定
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 这块处理environment-创建并配置环境,广播ApplicationEnvironmentPreparedEvent
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    // 设置spring.beaninfo.ignore属性,默认为true
    configureIgnoreBeanInfo(environment);
    //打印banner
    Banner printedBanner = printBanner(environment);
  // 这块开始创建并处理ApplicationContext
    context = createApplicationContext();
    // 获取SpringBootExceptionReporter
    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
        new Class[] { ConfigurableApplicationContext.class }, context);
    // 准备上下文
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    // 刷新上下文
    refreshContext(context);
    // 默认空方法
    afterRefresh(context, applicationArguments);
    //标明任务结束
    stopWatch.stop();
    //打印启动信息
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    }
    // 触发ApplicationStartedEvent事件广播
    listeners.started(context);
    //触发ApplicationRunner和CommandLineRunner run方法执行
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, listeners);
    throw new IllegalStateException(ex);
  }
  try {
    // 广播ApplicationReadyEvent事件
    listeners.running(context);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, null);
    throw new IllegalStateException(ex);
  }
  return context;
}

StopWatch


StopWatch 是什么呢?其是位于 org.springframework.util 包下的一个工具类,通过它可方便的对程序部分代码进行计时(ms级别),适用于同步单线程代码块。


简单总结一句,Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。


也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。

注意事项:


StopWatch对象不是设计为线程安全的,并且不适用同步。


一个StopWatch实例一次只能开启一个task,不能同时start多个task


在该task还没stop之前不能start一个新的task,必须在该task stop之后才能开启新的task


若要一次开启多个,需要new不同的StopWatch实例

【1】 运行监听器的获取与启动

① 获取SpringApplicationRunListeners

获取SpringApplicationRunListener,这里获取的是EventPublishingRunListener,然后实例化得到一个SpringApplicationRunListeners对象。

private SpringApplicationRunListeners getRunListeners(String[] args) {
  Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  // getSpringFactoriesInstances不再赘述
  return new SpringApplicationRunListeners(logger,
      getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

如下所示,EventPublishingRunListener实例内部维护了SimpleApplicationEventMulticaster,通过该实例将事件进行了广播。

public EventPublishingRunListener(SpringApplication application, String[] args) {
  this.application = application;
  this.args = args;
  this.initialMulticaster = new SimpleApplicationEventMulticaster();
  for (ApplicationListener<?> listener : application.getListeners()) {
    this.initialMulticaster.addApplicationListener(listener);
  }
}

② 启动SpringApplicationRunListener

这里会触发SpringApplicationRunListeners的starting方法,如下所示遍历内部维护的listeners挨个触发其starting方法。

void starting() {
  for (SpringApplicationRunListener listener : this.listeners) {
    listener.starting();
  }
}

以EventPublishingRunListener为例,其会广播ApplicationStartingEvent事件。除了ApplicationStartingEvent外还有ApplicationPreparedEvent、ApplicationReadyEvent、ApplicationFailedEvent事件。

@Override
public void starting() {
  this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

这里有如下所示五个监听器对ApplicationStartingEvent事件感兴趣。

0 = {RestartApplicationListener@1869} 
1 = {LoggingApplicationListener@1873} 
2 = {BackgroundPreinitializer@1874} 
3 = {DelegatingApplicationListener@1876} 
4 = {LiquibaseServiceLocatorApplicationListener@1881} 

【2】ConfigurableEnvironment的处理

这部分我们开始分析environment 的实例化与配置。

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

SpringApplication的prepareEnvironment方法。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments) {
  // Create and configure the environment
  ConfigurableEnvironment environment = getOrCreateEnvironment();
  //配置环境 
  configureEnvironment(environment, applicationArguments.getSourceArgs());
  //添加configurationProperties对应的ConfigurationPropertySourcesPropertySource
  ConfigurationPropertySources.attach(environment);
// 触发每个监听器的环境准备方法,本文这里是广播ApplicationEnvironmentPreparedEvent事件
// 增加三个PropertySource:
//RandomValuePropertySource {name='random'}
//OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}
//MapPropertySource {name='devtools'}
  listeners.environmentPrepared(environment);
  //将环境与SpringApplication绑定起来
  bindToSpringApplication(environment);
  if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
        deduceEnvironmentClass());
  }
  // 再次调用,更新configurationProperties对应的source
  ConfigurationPropertySources.attach(environment);
  return environment;
}

① getOrCreateEnvironment

getOrCreateEnvironment会根据webApplicationType实例化一个对象,本文这里是

tandardServletEnvironment实例。

StandardServletEnvironment继承StandardEnvironment继承自AbstractEnvironment。

private ConfigurableEnvironment getOrCreateEnvironment() {
  if (this.environment != null) {
    return this.environment;
  }
  switch (this.webApplicationType) {
  case SERVLET:
    return new StandardServletEnvironment();
  case REACTIVE:
    return new StandardReactiveWebEnvironment();
  default:
    return new StandardEnvironment();
  }
}

在其实例化过程中会触发父类AbstractEnvironment的customizePropertySources方法。

这里会放入servletContextInitParams、servletConfigInitParams对应的propertySource。

protected void customizePropertySources(MutablePropertySources propertySources) {
  propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
  propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
  if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
    propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
  }
  super.customizePropertySources(propertySources);
}

StandardEnvironment的customizePropertySources方法会放入systemEnvironment、systemProperties对应的propertySource。

protected void customizePropertySources(MutablePropertySources propertySources) {
  propertySources.addLast(
      new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
  propertySources.addLast(
      new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

也就是说实例化Environment后,其内部已经有了四个propertySource:servletContextInitParams、servletConfigInitParams、systemEnvironment、systemProperties。

② configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// 默认为true
  if (this.addConversionService) {
  // 获取到一个ConversionService 赋予environment
    ConversionService conversionService = ApplicationConversionService.getSharedInstance();
    environment.setConversionService((ConfigurableConversionService) conversionService);
  }
  // 尝试添加defaultProperties、springApplicationCommandLineArgs对应的propertySource,本文这里为空不添加
  configurePropertySources(environment, args);
  configureProfiles(environment, args);
}

方法逻辑梳理如下:

  • 获取到一个ConversionService 赋予environment
  • 尝试添加defaultPropertiesspringApplicationCommandLineArgs对应的propertySource,本文这里为空不添加
  • 将SpringApplication实例的Set additionalProfiles集合与spring.profiles.active信息合并并赋予environment的Set activeProfiles集合

① getSharedInstance

这里getSharedInstance比较有意思,其获取的单例方式是典型的双重校验锁并使用了volatile 关键字保证内存可见性。

// ApplicationConversionService extends FormattingConversionService
private static volatile ApplicationConversionService sharedInstance;
public static ConversionService getSharedInstance() {
  ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
  if (sharedInstance == null) {
    synchronized (ApplicationConversionService.class) {
      sharedInstance = ApplicationConversionService.sharedInstance;
      if (sharedInstance == null) {
        sharedInstance = new ApplicationConversionService();
        ApplicationConversionService.sharedInstance = sharedInstance;
      }
    }
  }
  return sharedInstance;
}

ApplicationConversionService实例化过程会进行一些默认配置,添加常见的、基础的转换器和格式化器。

public static void configure(FormatterRegistry registry) {
  DefaultConversionService.addDefaultConverters(registry);
  DefaultFormattingConversionService.addDefaultFormatters(registry);
  addApplicationFormatters(registry);
  addApplicationConverters(registry);
}

② configurePropertySources

如下所示获取到environment实例化后的sources ,然后尝试添加defaultProperties、springApplicationCommandLineArgs对应的source到environment中。

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
// 默认此时只有四个-在初始化时候添加的servletConfigInitParams servletContextInitParams 
// systemProperties  systemEnvironment
  MutablePropertySources sources = environment.getPropertySources();
//本文这里SpringApplication.defaultProperties == null
  if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
    sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
  }
// 尝试将命令行参数包装为source放到MutablePropertySources sources中
  if (this.addCommandLineProperties && args.length > 0) {
    //commandLineArgs
    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));
    }
  }
}

③ configureProfiles

获取SpringApplication的Set additionalProfiles与environment里面的spring.profiles.active进行合并然后赋予environment对象的成员Set activeProfiles 。本文这里都是空的数据对象。

// SpringApplication
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
  Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
  profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
  environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

additionalProfiles里面存放的是什么呢?要使用的其他配置文件值(在系统或命令行属性中设置的配置文件值之上),就是激活的配置文件路径(包含文件名称)。

③ ConfigurationPropertySources.attach

检测是否存在configurationProperties对应的PropertySource,初始化ConfigurationPropertySourcesPropertySource并放到sources 的首位。

// ConfigurationPropertySources
public static void attach(Environment environment) {
  Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
  MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
  // configurationProperties
  PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
  // 判断所属source是否一致,不一致则从sources移除
  if (attached != null && attached.getSource() != sources) {
    sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
    attached = null;
  }
// 放在首位  ConfigurationPropertySourcesPropertySource
  if (attached == null) {
    sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
        new SpringConfigurationPropertySources(sources)));
  }
}

方法执行后得到的sources如下所示:


需要注意的是在prepareEnvironment方法中这个方法触发了两次,最后一次会进入if (attached != null && attached.getSource() != sources)判断体中。

④ environmentPrepared

如下所示,遍历监听器触发environmentPrepared方法。以EventPublishingRunListener为例最终是广播ApplicationEnvironmentPreparedEvent事件。

void environmentPrepared(ConfigurableEnvironment environment) {
  for (SpringApplicationRunListener listener : this.listeners) {
    listener.environmentPrepared(environment);
  }
}

如下是对ApplicationEnvironmentPreparedEvent事件感兴趣的监听器。

0 = {RestartApplicationListener@1984} 
1 = {ConfigFileApplicationListener@3583} 
2 = {AnsiOutputApplicationListener@3584} 
3 = {LoggingApplicationListener@1985} 
4 = {BackgroundPreinitializer@1986} 
5 = {ClasspathLoggingApplicationListener@3585} 
6 = {DelegatingApplicationListener@1987} 
7 = {FileEncodingApplicationListener@3586} 

经过事件广播后,新增了三个PropertySource:

5 = {RandomValuePropertySource@3049} "RandomValuePropertySource {name='random'}"
6 = {OriginTrackedMapPropertySource@3050} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}"
7 = {MapPropertySource@3051} "MapPropertySource {name='devtools'}"

【3】configureIgnoreBeanInfo

SpringApplicationconfigureIgnoreBeanInfo方法,为System设置属性 spring.beaninfo.ignore= true

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
// spring.beaninfo.ignore
  if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
    Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
    // 为System设置属性 spring.beaninfo.ignore= true
    System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
  }
}

关于banner打印这里不展开分析,有兴趣的可以参考博文:SpringBoot中是如何打印banner的呢?



目录
相关文章
|
Java
springboot启动时执行
springboot启动时执行
63 0
|
5月前
|
XML Java 关系型数据库
Springboot启动时报错Property ‘mapperLocations‘ was not specified.
Springboot启动时报错Property ‘mapperLocations‘ was not specified.
178 2
|
6月前
|
Java 测试技术 数据库
SpringBoot启动时设置不加载数据库
SpringBoot启动时设置不加载数据库
368 0
|
6月前
|
存储 Java API
你了解SpringBoot启动时API相关信息是用什么数据结构存储的吗?(上篇)
你了解SpringBoot启动时API相关信息是用什么数据结构存储的吗?(上篇)
65 0
|
6月前
|
Java Linux Windows
windows解决SpringBoot启动时:APPLICATION FAILED TO START
windows解决SpringBoot启动时:APPLICATION FAILED TO START
289 0
如何修改springboot项目启动时的默认图标?
如何修改springboot项目启动时的默认图标?
109 0
如何修改springboot项目启动时的默认图标?
|
11月前
|
Java 容器
SpringBoot启动时都做了哪些事(三)?
SpringBoot启动时都做了哪些事(三)?
58 0
|
11月前
|
Java
SpringBoot启动时都做了哪些事(一)?
SpringBoot启动时都做了哪些事(一)?
49 0
|
11月前
|
Java 容器
SpringBoot启动时都做了哪些事(四)?
SpringBoot启动时都做了哪些事(四)?
67 0
|
缓存 Java 数据库
Springboot项目启动时加载数据库数据到内存
Springboot项目启动时加载数据库数据到内存
159 0