springboot源码(二)environment 环境准备

336 阅读4分钟

image.png

1 启动参数args解析

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

这个没啥好说的

// 解析args参数并封装为PropertySource,名字为commandLineArgs
// 正确格式--foo=bar
// **下面是解析逻辑**
public CommandLineArgs parse(String... args) {
   CommandLineArgs commandLineArgs = new CommandLineArgs();
   for (String arg : args) {
       // 必须以 -- 开头
      if (arg.startsWith("--")) {
         String optionText = arg.substring(2);
         String optionName;
         String optionValue = null;
         int indexOfEqualsSign = optionText.indexOf('=');
         // 截取key value
         if (indexOfEqualsSign > -1) {
            optionName = optionText.substring(0, indexOfEqualsSign);
            optionValue = optionText.substring(indexOfEqualsSign + 1);
         }
         else {optionName = optionText;}
         if (optionName.isEmpty()) { throw new IllegalArgumentException("Invalid argument syntax: " + arg);}
         commandLineArgs.addOptionArg(optionName, optionValue);
      }
      else { commandLineArgs.addNonOptionArg(arg);}
   }
   return commandLineArgs;
}

2 Environment环境准备

prepareEnvironment(listeners, bootstrapContext, applicationArguments);

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // Create and configure the environment
        // 这里获取了web环境推断,如果是SERVLET会返回ApplicationServletEnvironment
        //  ApplicationServletEnvironment构造会生成servletConfigInitParams/servletConfigInitParams/systemProperties/systemEnvironment属性源
        // systemProperties/systemEnvironment都是一些系统环境变量,jdk版本/用户/系统目录等等
        // 详见2.1
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 环境资源整合,把args也分装成一个PropertySource
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 把所有资源变成标准规范合在一起放在第一个
        ConfigurationPropertySources.attach(environment);
        // 发布了环境准备事件
        // 详见2.2
        listeners.environmentPrepared(bootstrapContext, environment);
        // 将defaultProperties移动到最后一位
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        // 将环境对象绑定到当前Spring应用
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        // 再次 把所有资源变成标准规范合在一起放在第一个
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

2.1 创建environment 环境对象

ConfigurableEnvironment environment = getOrCreateEnvironment();

根据先前的类型推断,我们需要创建各自不同的环境容器,虽然三个环境获得的类不同,其实是基本没有区别都继承了StandardServletEnvironment并且重写了一样的方法,只是新增了不同环境对于的不同PropertySource(mvc新增了servletConfigInitParams和servletConfigInitParams)

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {return this.environment;}
    // 更具先前的类型推断选择Environment对象
   switch (this.webApplicationType) {
   case SERVLET:
      return new ApplicationServletEnvironment();
   case REACTIVE:
      return new ApplicationReactiveWebEnvironment();
   default:
      return new ApplicationEnvironment();
   }
}

ApplicationServletEnvironment的类关系图

image-20220419204253603.png ApplicationServletEnvironment()的实例化啥都没有

这里就要看父类的父类的父类AbstractEnvironment的构造函数

protected AbstractEnvironment(MutablePropertySources propertySources) {
   this.propertySources = propertySources;
    // createPropertyResolver在StandardServletEnvironment中被重写
   this.propertyResolver = createPropertyResolver(propertySources);
    // 被StandardEnvironment重写
   customizePropertySources(propertySources);
}

StandardEnvironment是spring提供的标准的环境容器

为我们提供了systemEnvironment(系统的一些参数)和 systemProperties(jvm的一些参数)

public class StandardEnvironment extends AbstractEnvironment {
   /** System environment property source name: {@value}. */
   public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
   /** JVM system properties property source name: {@value}. */
   public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
   public StandardEnvironment() {
   }
   @Override
   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()));
   }
}

StandardServletEnvironment是springboot为Servlet服务提供的一个扩展环境

新增了servletContextInitParamsservletConfigInitParams但是好像都是空的,没有具体参数

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    /** Servlet context init parameters property source name: {@value}. */
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
    /** Servlet config init parameters property source name: {@value}. */
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
    public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        // 新增了两个PropertySource
        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);
    }
​
}

2.2 环境增强

到这里加上集合一共五个source, 此时我们的配置文件yml和properties都还没有读取,之后spring发布了一个环境准备完成事件

listeners.environmentPrepared(bootstrapContext, environment);

众多监听器中之一,EnvironmentPostProcessorApplicationListener监听到了这一事件

public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
   @Override
   public void onApplicationEvent(ApplicationEvent event) {
       // 监听到环境准备完成事件
      if (event instanceof ApplicationEnvironmentPreparedEvent) {
          // 传入ApplicationEnvironmentPreparedEvent进行扩展
         onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}
      if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent();}
      if (event instanceof ApplicationFailedEvent) {onApplicationFailedEvent();}
   }
   private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
      ConfigurableEnvironment environment = event.getEnvironment();
      SpringApplication application = event.getSpringApplication();
      // 获取了SpringFactories中所有的EnvironmentPostProcessor
      for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
                event.getBootstrapContext())) {
          // 循环调用回调接口
          postProcessor.postProcessEnvironment(environment, application);
      }
   }
}

spring获取springFactories中的EnvironmentPostProcessor,听名字就知道这是环境增强器

这里更像是对我们环境的一次扩充,读取yml,properties...等等一系列文件都可以在这完成,包括对一些${}动态参数的注入,或者随机数配置的赋值。 有些博客将这个增强器看作是获取环境参数的地方,其实不然,有些参数这时候并没有加载(之后我会详细介绍Apollo的加载顺序)。

springboot默认提供了9个环境增强器EnvironmentPostProcessor

image-20220419124656609.png

每个环境增强器各司其职,有解析json文件的,有随机值赋值的,一切看似很美好,但是我们似乎还是没有看到我们想看到的,properties和yml到底在哪? 由于springboot2.4之后,对配置环境进行了大改,对外部读取配置读取进行再一次增强,这一切都归功于ConfigDataEnvironmentPostProcessor,逻辑过于复杂,这里就不作展示 有兴趣可以看看这两篇博客

www.cnblogs.com/junzisi/p/1…

www.codenong.com/jsb307d1a5c…

3 springboot扩展-阿波罗Apollo

Apollo是如何扩展springboot应用的,首先提供了一个spring.factories

// 自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
// prepareContext阶段调用
org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
// 环境增强器
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer

这里主要关注ApplicationContextInitializerEnvironmentPostProcessor

官方使用文档 www.apolloconfig.com/#/zh/usage/…

图来自Apollo文档,但是有的类已经过时,ConfigFileApplicationListener已经过时(2.4.0之后被EnvironmentPostProcessorApplicationListener替换),但是依然不会影响主要功能,同时Apollo也提供了对2.4之后扩展的实现(详见apollo-client-config-data这个项目)

image-20220421154455158.png(2.4.0之后:环境准备阶段发布的环境准备事件被EnvironmentPostProcessorApplicationListener接收,然后调用所有EnvironmentPostProcessor进行增强)

3.1 阿波罗 加载配置的时机

3.1.1 EnvironmentPostProcessor

  @Override
  public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
    // 初始化必要参数
    initializeSystemProperty(configurableEnvironment);
    // 是否早加载,默认false    apollo.bootstrap.eagerLoad.enabled
    Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);
    // 直接返回
    if (!eagerLoadEnabled) {return;}
    // 是否从配置平台读取配置  apollo.bootstrap.enabled
    Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
​
    if (bootstrapEnabled) {
      DeferredLogger.enable();
      // 加载配置 详见3.2
      initialize(configurableEnvironment);
    }
  }

PropertySourcesConstants的一些配置参数名,也可以说是Apollo的引导类配置

public interface PropertySourcesConstants {
  String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
  String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
  String APOLLO_BOOTSTRAP_ENABLED = "apollo.bootstrap.enabled";
  String APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED = "apollo.bootstrap.eagerLoad.enabled";
  String APOLLO_BOOTSTRAP_NAMESPACES = "apollo.bootstrap.namespaces";
}

主要参数

apollo.bootstrap.enabled 开启从配置平台读取配置

apollo.bootstrap.eagerLoad.enabled 是否早加载

apollo.bootstrap.namespaces 需要使用的命名空间

我们可以看到EnvironmentPostProcessorApplicationListener优先于LoggingApplicationListener加载,如果配置文件中包含logging.level.root=info和logback-spring.xml中的参数,那么配置平台的配置将覆盖这些文件,实现日志的可配置化

image-20220421161442923.png

3.1.2 initialize

如果没有开启早加载(默认关闭,不然阿波罗的加载会没有日志),那阿波罗到底是在哪加载配置的呢?

不知道大家还有没有印象,在springboot new SpringApplication() 的时候注册了所有ApplicationContextInitializer,在prepareContext阶段的applyInitializers方法对所有ApplicationContextInitializer的方法进行了一次回调

此时所有的yml、proprties等等都已加载完毕,只需要把自己加载的文件放入配置文件集合的第一位,即可完成所有属性的覆盖,正常情况下此时其他框架已经不会修改environment了,那apollo完全可以为所欲为了

apollo提供的ApolloApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext context) {
  ConfigurableEnvironment environment = context.getEnvironment();
  // 还是判断一下是否开启了 apollo.bootstrap.enabled
  if (!environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false)) {
    logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
    return;
  }
  logger.debug("Apollo bootstrap config is enabled for context {}", context);
  // 加载配置详见3.2
  initialize(environment);
}

3.2 加载配置文件

Apollo提供了ConfigService.getConfig(namespace) 来实现获取不同命名空间的加载,具体实现就不看了

protected void initialize(ConfigurableEnvironment environment) {
  // 判重,如果环境已经加载了,那就啥都不干
  if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    DeferredLogger.replayTo();
    return;
  }
​
  // 获取所有namespaces
  String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
  logger.debug("Apollo bootstrap namespaces: {}", namespaces);
  // 用逗号分隔namespaces,因为我们的namespaces一般配置的都是以逗号分割字符串
  List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
​
  CompositePropertySource composite;
  // 获取配置工具类,存放apollo的配置项
  final ConfigUtil configUtil = ApolloInjector.getInstance(ConfigUtil.class);
  // 判断是否开启 apollo.property.names.cache.enable默认false
  if (configUtil.isPropertyNamesCacheEnabled()) {
    // 听说能启动更快
    composite = new CachedCompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
  } else {
    // 默认这个
    composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
  }
  // 获取每个命名空间的配置,加入composite
  for (String namespace : namespaceList) {
    Config config = ConfigService.getConfig(namespace);
    composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
  }
  // 把apollo的PropertySources放进第一个
  environment.getPropertySources().addFirst(composite);
}

这里把环境对象传了进来,创建了一个名为 ApolloBootstrapPropertySources 的PropertySource放入了配置集合首位。

4 总结

环境准备阶段是spring启动的第一个关键步骤,一次性为容器加载了所有配置,之后的每一步都必须依赖于当前的配置参数。如果需要对环境篇配置进行扩展和增强,推荐使用EnvironmentPostProcessor