【Springboot启动流程】02 初始化配置上下文

891 阅读6分钟

run方法【2】初始化配置上下文

书接上文,这里我们来解析run方法中的以下部分代码:

 public ConfigurableApplicationContext run(String... args) {
 //....
    try {
       ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
       ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
       configureIgnoreBeanInfo(environment);
       Banner printedBanner = printBanner(environment);
       context = createApplicationContext();
       context.setApplicationStartup(this.applicationStartup);
      //...
 }

2.1 配置启动参数

根据tracking,其实到

 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

这一步后,传入的args参数都不再出现,取而代之的是applicationArguments这个参数。

传入的是{},这里是将参数封装成对象了。

 public DefaultApplicationArguments(String... args) {
    Assert.notNull(args, "Args must not be null");
    this.source = new Source(args);
    this.args = args;
 }

实际上这里的参数,根据上面的代码观察可以知道:

  • 这个参数就是启动的main方法传入的参数。

2.2 准备环境对象

封装完参数后,接下来就开始构造

ConfigurableEnvironment

这一环境对象了。

再开始分析代码前,我们看看这个类名:

  • Configurable

    这也是Spring的一个特性:可配置。至于为什么这么说,后面看看代码再来具体看看。

  • environment

    这个单词的含义就是环境,换言之这里可能是包含了运行中所需要的,并且配置了的环境变量

    和上下文不同的是,这里的含义可能更多地是跟运行中构造的bean无关的内容。

接下来我们来看代码:

 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
       DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    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;
 }

2.2.1 构造环境对象

 ConfigurableEnvironment environment = getOrCreateEnvironment();
 //.......
 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();
    }
 }

这里就是根据SpringApplication中推断得知的webApplicationType,来返回不同类型的环境对象。

2.2.2 配置环境中的值

构造完对象之后,我们需要往里面填充数据。

 configureEnvironment(environment, applicationArguments.getSourceArgs());
 ​
     protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
         if (this.addConversionService) {
             environment.setConversionService(new ApplicationConversionService());
         }
         configurePropertySources(environment, args);
         configureProfiles(environment, args);
     }

第一步是:

  • 先判断是否添加ConversionService,如果有的话就给环境对象加上。(这里省略了这个

    new ApplicationConversionService() 所作工作的解析,其实这里是通过硬编码的方式将运行中需要的转换器放到了这个对象里。)

这个ConversionService,根据名字其实大概能推断一二:

  • Conversion,含义是转换

根据这个对象的构造方法,大概能知道转换的是什么:

 private ApplicationConversionService(StringValueResolver embeddedValueResolver, boolean unmodifiable) {
    if (embeddedValueResolver != null) {
       setEmbeddedValueResolver(embeddedValueResolver);
    }
    configure(this);
    this.unmodifiable = unmodifiable;
 }

这里通过变量名称,其实已经说明了,这里指的转换,其实指的是:

  • 值到值的类型转换

接下来是两个配置方法,环境对象的值填充应该大部分就在这里了,我们挨个看看。

2.2.2.1 configurePropertySources

 protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    if (!CollectionUtils.isEmpty(this.defaultProperties)) {
       DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
    }
    if (this.addCommandLineProperties && args.length > 0) {
       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));
       }
    }
 }

其实我们是能看出来的,前面都没有设置这个defaultProperties,args也就是个空数组,这个方法基本上就等于不执行了,只有

 sources.addFirst(new SimpleCommandLinePropertySource(args));

这里执行了一下,也就是把我们输入的参数封装成一个对象,放到source里面。

2.2.2.2 configureProfiles

 protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
 }

这个方法显然是使用了模板方法,留给子方法去实现相关功能的。

2.2.2.3 配置环境总结

根据我们常用的启动套路,实际上只是在defaultProperties里加入了对象化的,从启动方法里传下来的参数。

2.2.3 剩余的:通知以及赋值操作

环境对象已经配置好了,此时就是剩下的操作了。

 ConfigurationPropertySources.attach(environment);
 listeners.environmentPrepared(bootstrapContext, environment);
 //here
 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());
 }
 //here
 ConfigurationPropertySources.attach(environment);

注意看这里标出的两行一样的代码,这里第二次是因为在监听器的事件中,可能会修改这个environment。

其他没什么需要注意的点,就是把配置好的环境对象配置好了的事件发布一下,然后给需要环境的对象赋值存个备份。

2.3 配置忽略bean信息

这里没什么好说的,根据环境对象,配置一下系统中是否忽略bean信息。(默认为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());
    }
 }

这里有一个比较有意思的点,我们环境对象的值被写到JDK内置的System里面了,真的是假环境到真环境里面了。

2.4 打印banner

 Banner printedBanner = printBanner(environment);
 ​
 //
     private Banner printBanner(ConfigurableEnvironment environment) {
         //之前并没有配置过这个参数,因此这里是默认的CONSOLE
         if (this.bannerMode == Banner.Mode.OFF) {
             return null;
         }
         //这个resourceLoader也没有配置过,因此这里使用的是后面的这个
         ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(null);
         SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
         if (this.bannerMode == Mode.LOG) {
             return bannerPrinter.print(environment, this.mainApplicationClass, logger);
         }
         return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
     }

可以看到最后对应CONSOLE时,使用的是System.out来打印。

这一步执行完了,我们就能看到console里把banner打印出来了:

   .   ____          _            __ _ _
  /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
 ( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
  \/  ___)| |_)| | | | | || (_| |  ) ) ) )
   '  |____| .__|_| |_|_| |___, | / / / /
  =========|_|==============|___/=/_/_/_/
  :: Spring Boot ::                (v2.5.4)

其实这里的banner也是存在数组里的,对应的代码在SpringBootBanner里面,有兴趣再接着看,这里就不再赘述。

2.5 构建上下文对象

 context = createApplicationContext();

这一步的方法如下:

 protected ConfigurableApplicationContext createApplicationContext() {
     return this.applicationContextFactory.create(this.webApplicationType);
 }

在前面已经知道了这个webApplicationType对应的是SERVLET。而这个applicationContextFactory

我们没有配置,因此这里使用的是默认的ApplicationContextFactory.DEFAULT

这里的ApplicationContextFactory实际上是一个Functional接口,因此我们看看这个DEFAULT即可:

 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);
    }
 };

构建对象到这里就结束了,此时我们就清楚:

  • 在SpringBoot的默认构建中,使用的上下文对象类是AnnotationConfigServletWebServerApplicationContext,粗浅直译一下就是注解配置servlet网络服务应用上下文,基本上概括了构建应用的特点,所以在编码中,尽可能地将类特性通过类名展示出来,能很好地增强代码的可读性。

对于这个类,它的继承关系如图:www.processon.com/view/link/6…

可以看到此处涉及了多层的继承关系,如果使用到了再到类图中做对应解析。

2.6 设置applicationStartup

 context.setApplicationStartup(this.applicationStartup);

这里是一个简单的set方法,需要注意:

  • 此处使用的applicationStartup对象,是ApplicationStartup.DEFAULT,即:DefaultApplicationStartup

此时暂看不出来这个对象代表的含义是什么。

总结

到这里我们解析了最开始时提到的相关代码。总的来说,到这里为止,还是一些和容器相关的配置,以及后续使用到的对象的初始化,依然没有涉及到最核心的装配。

这里引入了一个新概念environment,并且第一次看到了我们常用的Application对象的创建。

这些代码中,显示引入的新的设计模式是:模板模式(见:2.2.2.2)。

其实在创建context对象的时候,用到了策略模式,至少Spring作者们是这么觉得的。

把createApplicationContext的注解放在下面,注意这里的Strategy

 /**
  * Strategy method used to create the {@link ApplicationContext}. By default this
  * method will respect any explicitly set application context class or factory before
  * falling back to a suitable default.
  * @return the application context (not yet refreshed)
  * @see #setApplicationContextClass(Class)
  * @see #setApplicationContextFactory(ApplicationContextFactory)
  */