spring boot 源码解析4-SpringApplication#run第4步

前言

之前我们分析了SpringApplication#run方法的前3步.在这里我们分析第4步–>创建一个DefaultApplicationArguments对象,调用prepareEnvironment方法.
StandardServletEnvironment-type-tree.png

分析

  1. 创建DefaultApplicationArguments.将启动时的参数传入到其构造器中.其构造器如下:

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

    在构造器中初始化了Source.其继承结构如下:

    《spring boot 源码解析4-SpringApplication#run第4步》

    在Source的构造器中调用了父类的SimpleCommandLinePropertySource的构造器.如下:

    public SimpleCommandLinePropertySource(String... args) {
        super(new SimpleCommandLineArgsParser().parse(args));
    }

    实例化了SimpleCommandLineArgsParser并调用其parse方法进行解析参数.代码如下:

    public CommandLineArgs parse(String... args) {
        CommandLineArgs commandLineArgs = new CommandLineArgs();
        for (String arg : args) {
            if (arg.startsWith("--")) {
                String optionText = arg.substring(2, arg.length());
                String optionName;
                String optionValue = null;
                if (optionText.contains("=")) {
                    optionName = optionText.substring(0, optionText.indexOf("="));
                    optionValue = optionText.substring(optionText.indexOf("=")+1, optionText.length());
                }
                else {
                    optionName = optionText;
                }
                if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
                    throw new IllegalArgumentException("Invalid argument syntax: " + arg);
                }
                commandLineArgs.addOptionArg(optionName, optionValue);
            }
            else {
                commandLineArgs.addNonOptionArg(arg);
            }
        }
        return commandLineArgs;
    }
    

    逻辑很简单,首先初始化了CommandLineArgs.然后遍历args.如果args是–开头的,就加入OptionArg中,否则加入到NonOptionArg中.

    解析完毕后,接着调用CommandLinePropertySource的构造器,代码如下:

    public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";
    public CommandLinePropertySource(T source) {
        super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
    }

    EnumerablePropertySource的构造器如下:

    public EnumerablePropertySource(String name, T source) {
        super(name, source);
    }

    PropertySource 构造器如下:

    public PropertySource(String name, T source) {
        Assert.hasText(name, "Property source name must contain at least one character");
        Assert.notNull(source, "Property source must not be null");
        this.name = name;
        this.source = source;
    }

    至此DefaultApplicationArguments初始化完毕.

  2. DefaultApplicationArguments初始化完毕后,调用SpringApplication#prepareEnvironment.代码如下:

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // 1. Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 2. 配置环境的信息
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 3. 通知所有的观察者,环境已经准备好了 
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }

    代码做了3件事:

    1. 获取或者创建ConfigurableEnvironment
    2. 配置ConfigurableEnvironment
    3. 通知所有的观察者,发送ApplicationEnvironmentPreparedEvent事件.
  3. 获取或者创建ConfigurableEnvironment调用的是getOrCreateEnvironment方法.代码如下:

    private ConfigurableEnvironment getOrCreateEnvironment() {
        // 1. 如果environment不为空则直接返回
        if (this.environment != null) {
            return this.environment;
        }
        // 2. 如果是web环境则直接实例化StandardServletEnvironment类
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        // 3. 如果不是web环境则直接实例化StandardEnvironment类 
        return new StandardEnvironment();
    }

    首先判断environment是否为空,如果不为空直接返回,否则 如果是web环境则直接实例化StandardServletEnvironment,否则返回StandardEnvironment.

    一般创建的是StandardServletEnvironment.

    StandardServletEnvironment类的继承结构如下:

    《spring boot 源码解析4-SpringApplication#run第4步》

    在StandardServletEnvironment实例化时,会触发AbstractEnvironment实例化.而在AbstractEnvironment的构造器中会调用customizePropertySources方法.代码如下:

    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
        if (logger.isDebugEnabled()) {
            logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
        }
    }

    而在StandardServletEnvironment中的customizePropertySources方法如下:

    
    /** 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";
    
    /** JNDI property source name: {@value} */
    public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
    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);
    }

    在该方法中添加了servletConfigInitParams,servletContextInitParams,jndiProperties对应的Source.
    在笔者的demo环境中, jndiProperties没有添加进去.

    然后调用其StandardEnvironment#customizePropertySources方法.添加MapPropertySource,SystemEnvironmentPropertySource.代码如下:

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
    
  4. 配置ConfigurableEnvironment,调用的是SpringApplication#configureEnvironment方法.代码如下:

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

    做了2件事:

    1. 配置PropertySources
    2. 配置Profiles

    configurePropertySources方法如下:

    protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        // 1. 如果defaultProperties不为空,则继续添加defaultProperties
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(
                    new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        // 2. 如果addCommandLineProperties为true并且有命令参数,
        // 分两步骤走:第一步存在commandLineArgs则继续设置属性;第二步commandLineArgs不存在则在头部添加commandLineArgs
        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(
                        name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
        // MutablePropertySources类中propertySourceList已经存在的属性为
    }

    做了2件事:

    1. 如果defaultProperties不为空,则继续添加defaultProperties

    在当前环境下defaultProperties没有添加进去.

    1. 如果addCommandLineProperties为true并且有命令参数,
      分两步骤走:第一步存在commandLineArgs则继续设置属性;第二步commandLineArgs不存在则在头部添加commandLineArgs

    那么该方法执行完毕后,MutablePropertySources类中propertySourceList已经存在的属性为:

    commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)

    配置Profiles,调用的是SpringApplication#configureProfiles方法.代码如下:

    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }

    做了3件事:

    1. 调用AbstractEnvironment#getActiveProfiles获得Profile的配置.Profile配置项为spring.profiles.active

    2. 调用AbstractEnvironment#getActiveProfiles方法获取激活的Profile添加到profiles中.

    3. 设置AbstractEnvironment的activeProfiles.

    AbstractEnvironment#getActiveProfiles代码如下:

    public String[] getActiveProfiles() {
        return StringUtils.toStringArray(doGetActiveProfiles());
    }

    调用了doGetActiveProfiles方法.代码如下:

    protected Set<String> doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }
    

    首先判断activeProfiles是否为空,如果不为空的话,就直接返回.否则调用PropertySourcesPropertyResolver#getProperty(“spring.profiles.active”) 进行查找.如果有配置的话,就加入到AbstractEnvironment的activeProfiles中.

    思考 为什么要这么做?

    spring boot 有一个参数优先级的概念.外置的配置优先于代码级别的.这里就是一个实现

  5. 通知所有的观察者,发送ApplicationEnvironmentPreparedEvent事件.调用的是SpringApplicationRunListeners#environmentPrepared方法.关于这里上篇文章有解释到.最终会调用EventPublishingRunListener#environmentPrepared 发送ApplicationEnvironmentPreparedEvent事件.

    对ApplicationEnvironmentPreparedEvent事件感兴趣的有:

    org.springframework.boot.context.config.ConfigFileApplicationListener, 
    org.springframework.boot.context.config.AnsiOutputApplicationListener, 
    org.springframework.boot.logging.LoggingApplicationListener, 
    org.springframework.boot.logging.ClasspathLoggingApplicationListener, 
    org.springframework.boot.autoconfigure.BackgroundPreinitializer, 
    org.springframework.boot.context.config.DelegatingApplicationListener, 
    org.springframework.boot.context.FileEncodingApplicationListener

    ConfigFileApplicationListener#onApplicationEvent代码如下:

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }

    调用onApplicationEnvironmentPreparedEvent方法.代码如下:

    private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(),
                    event.getSpringApplication());
        }
    }
    
    List<EnvironmentPostProcessor> loadPostProcessors() {
        return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
                getClass().getClassLoader());
    }

    实现逻辑如下:

    首先调用SpringFactoriesLoader加载EnvironmentPostProcessor.同时也将自己加入到postProcessors.排序后依次调用其postProcessEnvironment方法.

    对于当前场景来说. EnvironmentPostProcessor如下:

    org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor, 
    org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor, 
    org.springframework.boot.context.config.ConfigFileApplicationListener

    在spring-boot/META-INF/spring.factories中的配置如下:

    “`

Environment Post Processors

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
“`

SpringApplicationJsonEnvironmentPostProcessor#postProcessEnvironment代码如下:

```
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
        SpringApplication application) {
    String json = environment.resolvePlaceholders(
            "${spring.application.json:${SPRING_APPLICATION_JSON:}}");
    if (StringUtils.hasText(json)) {
        processJson(environment, json);
    }
}

private void processJson(ConfigurableEnvironment environment, String json) {
    try {
        JsonParser parser = JsonParserFactory.getJsonParser();
        Map<String, Object> map = parser.parseMap(json);
        if (!map.isEmpty()) {
            addJsonPropertySource(environment,
                    new MapPropertySource("spring.application.json", flatten(map)));
        }
    }
    catch (Exception ex) {
        logger.warn("Cannot parse JSON for spring.application.json: " + json, ex);
    }
}
```


实现逻辑如下:

1. 依次获取spring.application.json,SPRING_APPLICATION_JSON的值,如果没有配置的话,默认返回的空字符串.
2. 如果有配置的话,就调用processJson方法,在environment中添加MapPropertySource.name为spring.application.json.



CloudFoundryVcapEnvironmentPostProcessor#postProcessEnvironment代码如下:

```
public void postProcessEnvironment(ConfigurableEnvironment environment,
        SpringApplication application) {
    if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
        Properties properties = new Properties();
        addWithPrefix(properties, getPropertiesFromApplication(environment),
                "vcap.application.");
        addWithPrefix(properties, getPropertiesFromServices(environment),
                "vcap.services.");
        MutablePropertySources propertySources = environment.getPropertySources();
        if (propertySources.contains(
                CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
            propertySources.addAfter(
                    CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
                    new PropertiesPropertySource("vcap", properties));
        }
        else {
            propertySources
                    .addFirst(new PropertiesPropertySource("vcap", properties));
        }
    }
}
```

1. 首先调用CloudPlatform.CLOUD_FOUNDRY#isActive进行判断是否在CloudFoundry中.判断的逻辑为environment是否有VCAP_APPLICATION或者VCAP_SERVICES的配置 如下:

    ```
    public boolean isActive(Environment environment) {
            return environment.containsProperty("VCAP_APPLICATION")
                    || environment.containsProperty("VCAP_SERVICES");
        }
    ```

2. 如果是在CloudFoundry的话.则将vcap.application.\*, vcap.services.* 的配置加入到Properties中. 接下来判断 environment中是否有commandLineArgs的Sources.如果有的话,则添加到commandLineArgs中,否则添加名为vcap的PropertiesPropertySource.

> 一般情况下 CloudPlatform.CLOUD_FOUNDRY 返回的false.


ConfigFileApplicationListener#postProcessEnvironment代码如下:

```
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
        SpringApplication application) {
    addPropertySources(environment, application.getResourceLoader());
    configureIgnoreBeanInfo(environment);
    bindToSpringApplication(environment, application);
}
``` 

addPropertySources方法如下:

```
protected void addPropertySources(ConfigurableEnvironment environment,
        ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    new Loader(environment, resourceLoader).load();
}
```

做了两件事:

1. 调用了RandomValuePropertySource#addToEnvironment方法,向environment中名为systemEnvironment的添加了RandomValuePropertySource(名称为random)

    代码如下:

    ```
    /** System environment property source name: {@value} */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";

    public static void addToEnvironment(ConfigurableEnvironment environment) {
        environment.getPropertySources().addAfter(
                StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
        logger.trace("RandomValuePropertySource add to Environment");
    }
    ```

    正是因此我们才能在配置文件中使用${random.int} 生成随机值.

2. 初始化了Loader并调用其load方法.代码如下:

    ```
    public void load() {
        this.propertiesLoader = new PropertySourcesLoader();
        this.activatedProfiles = false;
        this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
        this.processedProfiles = new LinkedList<Profile>();

        // Pre-existing active profiles set via Environment.setActiveProfiles()
        // are additional profiles and config files are allowed to add more if
        // they want to, so don't call addActiveProfiles() here.
        Set<Profile> initialActiveProfiles = initializeActiveProfiles();
        this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
        if (this.profiles.isEmpty()) {
            for (String defaultProfileName : this.environment.getDefaultProfiles()) {
                Profile defaultProfile = new Profile(defaultProfileName, true);
                if (!this.profiles.contains(defaultProfile)) {
                    this.profiles.add(defaultProfile);
                }
            }
        }

        // The default profile for these purposes is represented as null. We add it
        // last so that it is first out of the queue (active profiles will then
        // override any settings in the defaults when the list is reversed later).
        this.profiles.add(null);

        while (!this.profiles.isEmpty()) {
            Profile profile = this.profiles.poll();
            for (String location : getSearchLocations()) {
                if (!location.endsWith("/")) {
                    // location is a filename already, so don't search for more
                    // filenames
                    load(location, null, profile);
                }
                else {
                    for (String name : getSearchNames()) {
                        load(location, name, profile);
                    }
                }
            }
            this.processedProfiles.add(profile);
        }

        addConfigurationProperties(this.propertiesLoader.getPropertySources());
    }
    ```

    处理步骤如下:
    1. 调用initializeActiveProfiles.获得ActiveProfiles.将未激活的Profiles加入到profiles中.如果profiles为空的话,就将spring.profiles.default配置的profile添加到profiles中.
    2. 依次遍历profiles中的profile.依次在classpath:/,classpath:/config/,file:./,file:./config/中加载application的配置.调用ConfigFileApplicationListener$Loader#load进行加载.
    3. 调用addConfigurationProperties,向environment中添加ConfigurationPropertySources.代码如下:

        ```
        private void addConfigurationProperties(MutablePropertySources sources) {
        List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();
        for (PropertySource<?> item : sources) {
            reorderedSources.add(item);
        }
        addConfigurationProperties(
                new ConfigurationPropertySources(reorderedSources));
        }

        private void addConfigurationProperties(
            ConfigurationPropertySources configurationSources) {
        MutablePropertySources existingSources = this.environment
                .getPropertySources();
        if (existingSources.contains(DEFAULT_PROPERTIES)) {
            existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);
        }
        else {
            existingSources.addLast(configurationSources);
        }
    }

        ```


    initializeActiveProfiles代码如下:

    ```
    private Set<Profile> initializeActiveProfiles() {
        // 1. 如果environment不含有spring.profiles.active和spring.profiles.include的配置话,返回空集合
        if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
                && !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
            return Collections.emptySet();
        }
        // Any pre-existing active profiles set via property sources (e.g. System
        // properties) take precedence over those added in config files.
        // 2. 调用bindSpringProfiles,生成SpringProfiles
        SpringProfiles springProfiles = bindSpringProfiles(
                this.environment.getPropertySources());
        Set<Profile> activeProfiles = new LinkedHashSet<Profile>(
                springProfiles.getActiveProfiles());
        activeProfiles.addAll(springProfiles.getIncludeProfiles());
        // 3. 调用maybeActivateProfiles.将activatedProfiles设为true
        maybeActivateProfiles(activeProfiles);
        return activeProfiles;
    }
    ``` 


    代码的逻辑如下:

    1. 如果environment不含有spring.profiles.active和spring.profiles.include的配置话,返回空集合

        > 注意: 当前的environment拥有的source有
        commandLineArgs、servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment, RandomValuePropertySource 如果想
不返回空的话,就需要在以上的source中有配置.最简单的方式是通过命令行的方式传入 --spring.profiles.active=you profiles 即可

    2. 调用bindSpringProfiles,生成SpringProfiles
    3. 调用maybeActivateProfiles.将activatedProfiles设为true 


    bindSpringProfiles代码如下:

    ```
    private SpringProfiles bindSpringProfiles(PropertySources propertySources) {
        SpringProfiles springProfiles = new SpringProfiles();
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(springProfiles,
                "spring.profiles");
        dataBinder.bind(new PropertySourcesPropertyValues(propertySources, false));
        springProfiles.setActive(resolvePlaceholders(springProfiles.getActive()));
        springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));
        return springProfiles;
    }
    ``` 

    逻辑如下:

    1.  初始化SpringProfiles和RelaxedDataBinder.RelaxedDataBinder读取的配置是前缀为 spring.profiles的配置.

   2. 实例化PropertySourcesPropertyValues,调用DataBinder#bind进行数据的绑定.

    3. 设置SpringProfiles的Active和Include属性.

    其中第2步中PropertySourcesPropertyValues的构造器代码如下:

    ```
    PropertySourcesPropertyValues(PropertySources propertySources,
        Collection<String> nonEnumerableFallbackNames,
        PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) {
    Assert.notNull(propertySources, "PropertySources must not be null");
    Assert.notNull(includes, "Includes must not be null");
    this.propertySources = propertySources;
    this.nonEnumerableFallbackNames = nonEnumerableFallbackNames;
    this.includes = includes;
    this.resolvePlaceholders = resolvePlaceholders;
    PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
            propertySources);
    for (PropertySource<?> source : propertySources) {
        processPropertySource(source, resolver);
    }
}
    ```

    其中最主要的代码是 初始化了 PropertySourcesPropertyResolver.并依次遍历environment中的PropertySources.调用processPropertySource方法进行处理.

    > 注意

    > 这里的source为 [SimpleCommandLinePropertySource {name='commandLineArgs'}, 

StubPropertySource {name=’servletConfigInitParams’},
StubPropertySource {name=’servletContextInitParams’},
MapPropertySource {name=’systemProperties’},
SystemEnvironmentPropertySource {name=’systemEnvironment’},
RandomValuePropertySource {name=’random’}]

processPropertySource 代码如下:

private void processPropertySource(PropertySource<?> source,
        PropertySourcesPropertyResolver resolver) {
    if (source instanceof CompositePropertySource) {
        processCompositePropertySource((CompositePropertySource) source, resolver);
    }
    else if (source instanceof EnumerablePropertySource) {
        processEnumerablePropertySource((EnumerablePropertySource<?>) source,
                resolver, this.includes);
    }
    else {
        processNonEnumerablePropertySource(source, resolver);
    }
}

这里的逻辑很简单:

  1. 如果 source 为 CompositePropertySource的话,调用processCompositePropertySource

    1. 如果 source 为 EnumerablePropertySource 的话,调用processEnumerablePropertySource
    2. 否则 调用 processNonEnumerablePropertySource

      这里我们需要看下各个source的类图.分别如下:

      SimpleCommandLinePropertySource 类图为:

      《spring boot 源码解析4-SpringApplication#run第4步》

      StubPropertySource 类图为:

      《spring boot 源码解析4-SpringApplication#run第4步》

      MapPropertySource 类图为:

      《spring boot 源码解析4-SpringApplication#run第4步》

      SystemEnvironmentPropertySource 类图为:

      《spring boot 源码解析4-SpringApplication#run第4步》

      RandomValuePropertySource 类图为:

      《spring boot 源码解析4-SpringApplication#run第4步》
      因此我们就明白了:

      对于 SimpleCommandLinePropertySource, MapPropertySource, SystemEnvironmentPropertySource 会调用processEnumerablePropertySource方法.
      对于 StubPropertySource, RandomValuePropertySource 会调用 processNonEnumerablePropertySource 方法.

      processEnumerablePropertySource 代码如下:

      private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
          PropertySourcesPropertyResolver resolver,
          PropertyNamePatternsMatcher includes) {
      if (source.getPropertyNames().length > 0) {
          for (String propertyName : source.getPropertyNames()) {
              if (includes.matches(propertyName)) {
                  Object value = getEnumerableProperty(source, resolver, propertyName);
                  putIfAbsent(propertyName, value, source);
              }
          }
      }
      }

      逻辑很简单,首先判断source是否有值. 如果有的话,依次遍历之.然后调用PropertyNamePatternsMatcher的matches方法判断是否匹配.如果匹配的话,就进行加入.这里的
      PropertyNamePatternsMatcher为PropertyNamePatternsMatcher.ALL 其matches方法是直接返回true的.代码如下:

      @Override
      public boolean matches(String propertyName) {
          return true;
      }

      而 processNonEnumerablePropertySource 代码如下:

      private void processNonEnumerablePropertySource(PropertySource<?> source,
          PropertySourcesPropertyResolver resolver) {
      // We can only do exact matches for non-enumerable property names, but
      // that's better than nothing...
      if (this.nonEnumerableFallbackNames == null) {
          return;
      }
      for (String propertyName : this.nonEnumerableFallbackNames) {
          if (!source.containsProperty(propertyName)) {
              continue;
          }
          Object value = null;
          try {
              value = resolver.getProperty(propertyName, Object.class);
          }
          catch (RuntimeException ex) {
              // Probably could not convert to Object, weird, but ignorable
          }
          if (value == null) {
              value = source.getProperty(propertyName.toUpperCase());
          }
          putIfAbsent(propertyName, value, source);
      }
      }

      这里首先判断nonEnumerableFallbackNames是否为null,如果为null的话,直接return.悲催的是,这里的nonEnumerableFallbackNames 为null.因此不会执行后续的代码.

      接下来调用DataBinder#bind方法.代码如下:

      public void bind(PropertyValues pvs) {
      MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ?
              (MutablePropertyValues) pvs : new MutablePropertyValues(pvs);
      doBind(mpvs);
      }

      这里做了2件事:

      1. 判断PropertyValues是否为MutablePropertyValues.如果不是,实例化MutablePropertyValues. 不幸的是这里传入的PropertySourcesPropertyValues 不是MutablePropertyValues的实例,因此会实例化MutablePropertyValues. PropertySourcesPropertyValues类图如下:

        《spring boot 源码解析4-SpringApplication#run第4步》

        MutablePropertyValues 构造器如下:

        public MutablePropertyValues(PropertyValues original) {
        // We can optimize this because it's all new:
        // There is no replacement of existing property values.
        if (original != null) {
        PropertyValue[] pvs = original.getPropertyValues();
        this.propertyValueList = new ArrayList<PropertyValue>(pvs.length);
        for (PropertyValue pv : pvs) {
            this.propertyValueList.add(new PropertyValue(pv));
        }
        }
        else {
        this.propertyValueList = new ArrayList<PropertyValue>(0);
        }
        }

        逻辑很简单,首先判断PropertyValues是否为null.如果不为null的话,依次遍历PropertyValues的PropertyValues.加入到MutablePropertyValues中的propertyValueList.这样MutablePropertyValues就变相的拥有了environment中source的配置。

      2. 调用 doBind 进行绑定.代码如下:

        protected void doBind(MutablePropertyValues mpvs) {
        checkAllowedFields(mpvs);
        checkRequiredFields(mpvs);
        applyPropertyValues(mpvs);
        }

        作了3件事

        1. 检查是否包含不允许的字段
        2. 检查RequiredFields 是否不存在,如果不存在的话,就向error processor 添加FieldError.
        3. 调用applyPropertyValues方法进行处理.代码如下:

          protected void applyPropertyValues(MutablePropertyValues mpvs) {
          try {
          // Bind request parameters onto target object.
          getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
          }
          catch (PropertyBatchUpdateException ex) {
          // Use bind error processor to create FieldErrors.
          for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
          getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
          }
          }
          }

          调用

          protected ConfigurablePropertyAccessor getPropertyAccessor() {
          return getInternalBindingResult().getPropertyAccessor();
          }
          

          调用

          protected AbstractPropertyBindingResult getInternalBindingResult() {
          if (this.bindingResult == null) {
          initBeanPropertyAccess();
          }
          return this.bindingResult;
          }

          由于此时bindingResult为null,因此调用initBeanPropertyAccess方法进行初始化.代码如下:

          public void initBeanPropertyAccess() {
          Assert.state(this.bindingResult == null,
          "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
          this.bindingResult = createBeanPropertyBindingResult();
          }

          调用createBeanPropertyBindingResult方法.代码如下:

          protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
          BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),
              getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
          if (this.conversionService != null) {
          result.initConversion(this.conversionService);
          }
          if (this.messageCodesResolver != null) {
          result.setMessageCodesResolver(this.messageCodesResolver);
          }
          return result;
          }

          因此这里创建了 BeanPropertyBindingResult.

          然后调用BeanPropertyBindingResult#getPropertyAccessor方法.代码如下:

          public final ConfigurablePropertyAccessor getPropertyAccessor() {
          if (this.beanWrapper == null) {
          this.beanWrapper = createBeanWrapper();
          this.beanWrapper.setExtractOldValueForEditor(true);
          this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
          this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
          }
          return this.beanWrapper;
          }

          由于这里的beanWrapper为null,因此调用createBeanWrapper方法进行初始化.代码如下:

          protected BeanWrapper createBeanWrapper() {
          Assert.state(this.target != null, "Cannot access properties on null bean instance '" + getObjectName() + "'!");
          return PropertyAccessorFactory.forBeanPropertyAccess(this.target);
          }

          调用

          public static BeanWrapper forBeanPropertyAccess(Object target) {
          return new BeanWrapperImpl(target);
          }

          在获得PropertyAccessor后,调用其setPropertyValues方法.代码如下:

          public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
          throws BeansException {
          List<PropertyAccessException> propertyAccessExceptions = null;
          List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
          ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
          for (PropertyValue pv : propertyValues) {
          try {
          // This method may throw any BeansException, which won't be caught
          // here, if there is a critical failure such as no matching field.
          // We can attempt to deal only with less serious exceptions.
          setPropertyValue(pv);
          }
          catch (NotWritablePropertyException ex) {
          if (!ignoreUnknown) {
              throw ex;
          }
          // Otherwise, just ignore it and continue...
          }
          catch (NullValueInNestedPathException ex) {
          if (!ignoreInvalid) {
              throw ex;
          }
          // Otherwise, just ignore it and continue...
          }
          catch (PropertyAccessException ex) {
          if (propertyAccessExceptions == null) {
              propertyAccessExceptions = new LinkedList<PropertyAccessException>();
          }
          propertyAccessExceptions.add(ex);
          }
          }
          if (propertyAccessExceptions != null) {
          PropertyAccessException[] paeArray =
              propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
          throw new PropertyBatchUpdateException(paeArray);
          }
          }

          逻辑很简单,遍历propertyValues.调用setPropertyValue方法进行处理.如果在处理过程中,出现了PropertyAccessException异常,就加入到propertyAccessExceptions中.最后判断是否propertyAccessExceptions不为空.如果是的话,抛出PropertyBatchUpdateException异常.

          调用RelaxedBeanWrapper#setPropertyValue方法.代码如下:

          public void setPropertyValue(PropertyValue pv) throws BeansException {
          try {
          super.setPropertyValue(pv);
          }
          catch (NotWritablePropertyException ex) {
          PropertyOrigin origin = OriginCapablePropertyValue.getOrigin(pv);
          if (isBenign(origin)) {
              logger.debug("Ignoring benign property binding failure", ex);
              return;
          }
          if (origin == null) {
              throw ex;
          }
          throw new RelaxedBindingNotWritablePropertyException(ex, origin);
          }
          }

          调用 AbstractNestablePropertyAccessor#setPropertyValue方法.代码如下:

              public void setPropertyValue(PropertyValue pv) throws BeansException {
          PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
          if (tokens == null) {
          String propertyName = pv.getName();
          AbstractNestablePropertyAccessor nestedPa;
          try {
          nestedPa = getPropertyAccessorForPropertyPath(propertyName);
          }
          catch (NotReadablePropertyException ex) {
          throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                  "Nested property in path '" + propertyName + "' does not exist", ex);
          }
          tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
          if (nestedPa == this) {
          pv.getOriginalPropertyValue().resolvedTokens = tokens;
          }
          nestedPa.setPropertyValue(tokens, pv);
          }
          else {
          setPropertyValue(tokens, pv);
          }
          }

          调用

          protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
          if (tokens.keys != null) {
          processKeyedProperty(tokens, pv);
          }
          else {
          processLocalProperty(tokens, pv);
          }
          }

          这里只拿processLocalProperty举例. 如果PropertyValue 需要进行转换的话,就会调用propertyEditorRegistry中的ConversionService进行转换.这里是spring的知识了.就不再深入了.

          我们把视线回到ConfigFileApplicationListener$Loader#bindSpringProfiles方法.现在我们该执行如下代码了:

          《spring boot 源码解析4-SpringApplication#run第4步》

        resolvePlaceholders代码如下:

        private List<String> resolvePlaceholders(List<String> values) {
        List<String> resolved = new ArrayList<String>();
        for (String value : values) {
            resolved.add(this.environment.resolvePlaceholders(value));
        }
        return resolved;
        }

        逻辑很简单,遍历Active Profiles .调用 AbstractEnvironment#resolvePlaceholders 将形如 ${Placeholder} 进行解析.将解析后的值加入到resolved中进行返回.

        AbstractEnvironment#resolvePlaceholders 代码如下:

        public String resolvePlaceholders(String text) {
        if (this.nonStrictHelper == null) {
        this.nonStrictHelper = createPlaceholderHelper(true);
        }
        return doResolvePlaceholders(text, this.nonStrictHelper);
        }

        调用

            private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
            return getPropertyAsRawString(placeholderName);
        }
        });
        }
        

        调用

        public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return parseStringValue(value, placeholderResolver, new HashSet<String>());
        }

        调用

        protected String parseStringValue(
        String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
        StringBuilder result = new StringBuilder(value);
        int startIndex = value.indexOf(this.placeholderPrefix);
        while (startIndex != -1) {
        int endIndex = findPlaceholderEndIndex(result, startIndex);
        if (endIndex != -1) {
            String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
            String originalPlaceholder = placeholder;
            if (!visitedPlaceholders.add(originalPlaceholder)) {
                throw new IllegalArgumentException(
                        "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
            }
            // Recursive invocation, parsing placeholders contained in the placeholder key.
            placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
            // Now obtain the value for the fully resolved key...
            String propVal = placeholderResolver.resolvePlaceholder(placeholder);
            if (propVal == null && this.valueSeparator != null) {
                int separatorIndex = placeholder.indexOf(this.valueSeparator);
                if (separatorIndex != -1) {
                    String actualPlaceholder = placeholder.substring(0, separatorIndex);
                    String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                    propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                    if (propVal == null) {
                        propVal = defaultValue;
                    }
                }
            }
            if (propVal != null) {
                // Recursive invocation, parsing placeholders contained in the
                // previously resolved placeholder value.
                propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                if (logger.isTraceEnabled()) {
                    logger.trace("Resolved placeholder '" + placeholder + "'");
                }
                startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
            }
            else if (this.ignoreUnresolvablePlaceholders) {
                // Proceed with unprocessed value.
                startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
            }
            else {
                throw new IllegalArgumentException("Could not resolve placeholder '" +
                        placeholder + "'" + " in value \"" + value + "\"");
            }
            visitedPlaceholders.remove(originalPlaceholder);
        }
        else {
            startIndex = -1;
        }
        }
        return result.toString();
        }

        这里的逻辑很简单.首先判断是否有${ ,如果有的话,就进行替换.否则直接返回.这里涉及Placeholder的解析.我们后续会有文章进行分析的.

        接下来我们为SpringProfiles设置Include,不过这里springProfiles#getInclude返回的是空集合,因此这里就不在分析了.代码如下:

        springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));

        经过漫长的道路,我们来到了maybeActivateProfiles方法.代码如下:

        private void maybeActivateProfiles(Set<Profile> profiles) {
        if (this.activatedProfiles) {
            if (!profiles.isEmpty()) {
                this.logger.debug("Profiles already activated, '" + profiles
                        + "' will not be applied");
            }
            return;
        }
        if (!profiles.isEmpty()) {
            addProfiles(profiles);
            this.logger.debug("Activated profiles "
                    + StringUtils.collectionToCommaDelimitedString(profiles));
            this.activatedProfiles = true;
            // 删除默认的Profiles
            removeUnprocessedDefaultProfiles();
        }
        }

      逻辑很简单:

      1. 如果activatedProfiles为true的话,代表已经激活过了,就不再继续后续处理.直接return.
      2. 否则调用addProfiles方法.将activate Profiles 加入到Loader中的profiles中.代码如下:

        private void addProfiles(Set<Profile> profiles) {
        for (Profile profile : profiles) {
            this.profiles.add(profile);
            // 如果environment中的ActiveProfile和profile不等的话调用prependProfile.
            if (!environmentHasActiveProfile(profile.getName())) {
                // If it's already accepted we assume the order was set
                // intentionally
                prependProfile(this.environment, profile);
            }
        }
        }
      3. 将activatedProfiles设为true.
      4. 删除默认的Profiles.代码如下:

            private void removeUnprocessedDefaultProfiles() {
        for (Iterator<Profile> iterator = this.profiles.iterator(); iterator
                .hasNext();) {
            if (iterator.next().isDefaultProfile()) {
                iterator.remove();
            }
        }
        }

        如何判断Profile为默认的呢? 调用Profile#isDefaultProfile方法.代码如下:

        public boolean isDefaultProfile() {
        return this.defaultProfile;
        }

        而defaultProfile默认为false.

  2. 我们继续分析ConfigFileApplicationListener$Loader#load方法中的第2步–>加载配置.代码如下:

    while (!this.profiles.isEmpty()) {
                Profile profile = this.profiles.poll();
                for (String location : getSearchLocations()) {
                    if (!location.endsWith("/")) {
                        // location is a filename already, so don't search for more
                        // filenames
                        load(location, null, profile);
                    }
                    else {
                        for (String name : getSearchNames()) {
                            load(location, name, profile);
                        }
                    }
                }
                this.processedProfiles.add(profile);
    }

    逻辑如下:

    1. 遍历profiles
    2. 在SearchLocations中依次进行加载profile的配置.这里有判断location是否是已/结尾的.如果不是以/结尾的,就意味着明确了文件的路径,直接加载即可.否则遍历location下的SearchName进行加载.

      getSearchLocations代码如下:

      private Set<String> getSearchLocations() {
          Set<String> locations = new LinkedHashSet<String>();
          // User-configured settings take precedence, so we do them first
          if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
              for (String path : asResolvedSet(
                      this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
                  if (!path.contains("$")) {
                      path = StringUtils.cleanPath(path);
                      if (!ResourceUtils.isUrl(path)) {
                          path = ResourceUtils.FILE_URL_PREFIX + path;
                      }
                  }
                  locations.add(path);
              }
          }
          locations.addAll(
      asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
                          DEFAULT_SEARCH_LOCATIONS));
          return locations;
      }

      首先判断environment是否有spring.config.location的配置,如果有的话就加入的搜索路径中.然后加入默认的路径–>classpath:/,classpath:/config/,file:./,file:./config/

      getSearchNames 代码如下:

      private Set<String> getSearchNames() {
          if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
              return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
                      null);
          }
          return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
      }

      还是同样的套路,如果environment有配置spring.config.name的话,就用配置的.否则就返回默认的application的.

      到这里我们就明白了. 在classpath:/,classpath:/config/,file:./,file:./config/ 路径下,加载 application-profile 的配置文件.

      加载代码如下:

      private void load(String location, String name, Profile profile) {
          String group = "profile=" + (profile == null ? "" : profile);
          if (!StringUtils.hasText(name)) {
              // Try to load directly from the location
              loadIntoGroup(group, location, profile);
          }
          else {
              // Search for a file with the given name
              for (String ext : this.propertiesLoader.getAllFileExtensions()) {
                  if (profile != null) {
                      // Try the profile-specific file
                      loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                              null);
                      for (Profile processedProfile : this.processedProfiles) {
                          if (processedProfile != null) {
                              loadIntoGroup(group, location + name + "-"
                                      + processedProfile + "." + ext, profile);
                          }
                      }
                      // Sometimes people put "spring.profiles: dev" in
                      // application-dev.yml (gh-340). Arguably we should try and error
                      // out on that, but we can be kind and load it anyway.
                      loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                              profile);
                  }
                  // Also try the profile-specific section (if any) of the normal file
                  loadIntoGroup(group, location + name + "." + ext, profile);
              }
          }
      }

      逻辑如下:

      1. 如果name等于null的话,直接调用loadIntoGroup
      2. 循环遍历可加载文件的扩展名,对于当前场景来说,可加载的扩展名为–> properties, xml, yml, yaml.

        如果profile不等于null的话, 调用loadIntoGroup,加载的location为 location/application-profile.ext

        循环遍历processedProfiles, 调用loadIntoGroup,加载的location为 location/application-processedProfile.ext
        调用loadIntoGroup,加载的location为 location/application-pprofile.ext,这是因为人们有时会在application-dev.yml 中配置 spring.profiles: dev 这种情况下,也应该进行加载.
        最后,调用loadIntoGroup,加载的location为location/application.txt.

      loadIntoGroup方法没有做什么,直接调用的doLoadIntoGroup方法.代码如下:

      private PropertySource<?> doLoadIntoGroup(String identifier, String location,
              Profile profile) throws IOException {
          Resource resource = this.resourceLoader.getResource(location);
          PropertySource<?> propertySource = null;
          StringBuilder msg = new StringBuilder();
          if (resource != null && resource.exists()) {
              String name = "applicationConfig: [" + location + "]";
              String group = "applicationConfig: [" + identifier + "]";
              propertySource = this.propertiesLoader.load(resource, group, name,
                      (profile == null ? null : profile.getName()));
              if (propertySource != null) {
                  msg.append("Loaded ");
                  handleProfileProperties(propertySource);
              }
              else {
                  msg.append("Skipped (empty) ");
              }
          }
          else {
              msg.append("Skipped ");
          }
          msg.append("config file ");
          msg.append(getResourceDescription(location, resource));
          if (profile != null) {
              msg.append(" for profile ").append(profile);
          }
          if (resource == null || !resource.exists()) {
              msg.append(" resource not found");
              this.logger.trace(msg);
          }
          else {
              this.logger.debug(msg);
          }
          return propertySource;
      }
      

      代码比较长,但是有用的是:

      1. 调用resourceLoader进行加载.
      2. 如果加载成功的话,调用propertiesLoader进行加载.如果加载成功的话,调用handleProfileProperties方法,

      PropertySourcesLoader#load方法如下:

      public PropertySource<?> load(Resource resource, String group, String name,
          String profile) throws IOException {
      if (isFile(resource)) {
          String sourceName = generatePropertySourceName(name, profile);
          for (PropertySourceLoader loader : this.loaders) {
              if (canLoadFileExtension(loader, resource)) {
                  PropertySource<?> specific = loader.load(sourceName, resource,
                          profile);
                  addPropertySource(group, specific, profile);
                  return specific;
              }
          }
      }
      return null;
      }

      逻辑如下:

      1. 如果是文件的话,就调用PropertySourceLoader尝试加载.否则直接返回null.
      2. 生成sourceName.生成规则为

        (profile == null ? name : name + “#” + profile)

      3. 遍历PropertySourceLoader,如果能进行加载的话,调用其load方法进行加载.然后将其返回值PropertySource加入到PropertySourcesLoader#propertySources中.

      这里有必要说明一下PropertySourcesLoader中的loaders. 该类的初始化调用链如下:

      《spring boot 源码解析4-SpringApplication#run第4步》

      其构造器如下:

      public PropertySourcesLoader(MutablePropertySources propertySources) {
      Assert.notNull(propertySources, "PropertySources must not be null");
      this.propertySources = propertySources;
      this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
              getClass().getClassLoader());
      }

      可以发现loaders 是通过SpringFactoriesLoader加载META-INF/spring.factories中org.springframework.boot.env.PropertySourceLoader的配置.其中,配置如下:

      
      # PropertySource Loaders
      
      org.springframework.boot.env.PropertySourceLoader=\
      org.springframework.boot.env.PropertiesPropertySourceLoader,\
      org.springframework.boot.env.YamlPropertySourceLoader

      至此,我们就知道了loaders 为 PropertiesPropertySourceLoader,YamlPropertySourceLoader

      canLoadFileExtension 代码如下:

      private boolean canLoadFileExtension(PropertySourceLoader loader, Resource resource) {
      String filename = resource.getFilename().toLowerCase();
      for (String extension : loader.getFileExtensions()) {
          if (filename.endsWith("." + extension.toLowerCase())) {
              return true;
          }
      }
      return false;
      }

      可以发现是通过比较PropertySourceLoader支持的文件扩展名是否和当前的文件的后缀相同进行判断.

      通过源码可知.PropertiesPropertySourceLoader支持的扩展名为–> properties, xml.
      YamlPropertySourceLoader支持的扩展名为–> yml, yaml.

      接下来我们分别看下PropertySourceLoader中load方法的实现.

      PropertiesPropertySourceLoader#load方法如下:

      public PropertySource<?> load(String name, Resource resource, String profile)
          throws IOException {
      if (profile == null) {
          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
          if (!properties.isEmpty()) {
              return new PropertiesPropertySource(name, properties);
          }
      }
      return null;
      }

      直接通过PropertiesLoaderUtils直接进行加载,最后返回PropertiesPropertySource.

      YamlPropertySourceLoader#load 代码如下:

          public PropertySource<?> load(String name, Resource resource, String profile)
          throws IOException {
      if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
          Processor processor = new Processor(resource, profile);
          Map<String, Object> source = processor.process();
          if (!source.isEmpty()) {
              return new MapPropertySource(name, source);
          }
      }
      return null;
      }
      

      首先判断当前类路径是否存在org.yaml.snakeyaml.Yaml.如果存在的话,再进行加载,成功后返回MapPropertySource.

      加载完毕后,我们来到了 PropertySourcesLoader#addPropertySource.代码如下:

      private void addPropertySource(String basename, PropertySource<?> source,
          String profile) {
      
      if (source == null) {
          return;
      }
      
      if (basename == null) {
          this.propertySources.addLast(source);
          return;
      }
      
      EnumerableCompositePropertySource group = getGeneric(basename);
      group.add(source);
      logger.trace("Adding PropertySource: " + source + " in group: " + basename);
      if (this.propertySources.contains(group.getName())) {
          this.propertySources.replace(group.getName(), group);
      }
      else {
          this.propertySources.addFirst(group);
      }
      }

      做了4件事:

      1. 如果source加载失败 直接返回
      2. 如果basename没有指定,直接加入propertySources后返回.一般basename都是有值的
      3. 从propertySources中获得EnumerableCompositePropertySource后加入source
      4. 加入propertySources,如果之前存在的话,就进行替换
    3. 加载完毕后,加入到processedProfiles中.

  3. 我们继续分析ConfigFileApplicationListener$Loader#load方法中的第3步–>调用addConfigurationProperties.代码如下:

    private void addConfigurationProperties(MutablePropertySources sources) {
            List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();
            for (PropertySource<?> item : sources) {
                reorderedSources.add(item);
            }
            addConfigurationProperties(
                    new ConfigurationPropertySources(reorderedSources));
        }
    
        private void addConfigurationProperties(
                ConfigurationPropertySources configurationSources) {
            MutablePropertySources existingSources = this.environment
                    .getPropertySources();
            if (existingSources.contains(DEFAULT_PROPERTIES)) {
                existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);
            }
            else {
                existingSources.addLast(configurationSources);
            }
        }

    逻辑很简单,如果environment中有defaultProperties的话,就添加ConfigurationPropertySources到defaultProperties.否则直接进行添加.

    在笔者的测试环境中,是直接加入到environment中的.因此现在environment中有如下source:
    [SimpleCommandLinePropertySource {name=’commandLineArgs’}, StubPropertySource {name=’servletConfigInitParams’}, StubPropertySource {name=’servletContextInitParams’}, MapPropertySource {name=’systemProperties’}, SystemEnvironmentPropertySource {name=’systemEnvironment’}, RandomValuePropertySource {name=’random’}, ConfigurationPropertySources {name=’applicationConfigurationProperties’}]

  4. 终于我们分析完了 ConfigFileApplicationListener中的postProcessEnvironment的第一步.不容易啊. 接下来我们分析第2步–> 调用ConfigFileApplicationListener#configureIgnoreBeanInfo,完成spring.beaninfo.ignore的配置,一般默认为true.代码如下:

    public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
    
    private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        if (System.getProperty(
                CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
            RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,
                    "spring.beaninfo.");
            Boolean ignore = resolver.getProperty("ignore", Boolean.class, Boolean.TRUE);
            System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
                    ignore.toString());
        }
    }
  5. ConfigFileApplicationListener对于 postProcessEnvironment处理的第三步是调用 bindToSpringApplication方法.代码如下:

    protected void bindToSpringApplication(ConfigurableEnvironment environment,
            SpringApplication application) {
        PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>(
                application);
        binder.setTargetName("spring.main");
        binder.setConversionService(this.conversionService);
        binder.setPropertySources(environment.getPropertySources());
        try {
            binder.bindPropertiesToTarget();
        }
        catch (BindException ex) {
            throw new IllegalStateException("Cannot bind to SpringApplication", ex);
        }
    }

    3件事:

    1. 初始化PropertiesConfigurationFactory, PropertiesConfigurationFactory实现了FactoryBean,MessageSourceAware,InitializingBean 接口. 其构造器将SpringApplication传入了进去,这样PropertiesConfigurationFactory就持有了SpringApplication的实例.代码如下:

      public PropertiesConfigurationFactory(T target) {
          Assert.notNull(target, "target must not be null");
          this.target = target;
      }
    2. 设置PropertiesConfigurationFactory的TargetName,ConversionService,PropertySources.代码如下:

      binder.setTargetName("spring.main");
      binder.setConversionService(this.conversionService);
      binder.setPropertySources(environment.getPropertySources());

      这里很明显 将PropertiesConfigurationFactory的TargetName 设为 spring.main,
      将environment中的PropertySources赋值给PropertiesConfigurationFactory.
      最后将ConfigFileApplicationListener中的conversionService进行赋值.

      这里有必要说一下 ConfigFileApplicationListener 中的conversionService.
      该属性是在ConfigFileApplicationListener实例化时进行赋值的.代码如下:

      private final ConversionService conversionService = new DefaultConversionService();

      DefaultConversionService 的构造器如下:

      public DefaultConversionService() {
          addDefaultConverters(this);
      }

      调用

      public static void addDefaultConverters(ConverterRegistry converterRegistry) {
      addScalarConverters(converterRegistry);
      addCollectionConverters(converterRegistry);
      
      converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
      if (jsr310Available) {
          Jsr310ConverterRegistrar.registerJsr310Converters(converterRegistry);
      }
      
      converterRegistry.addConverter(new ObjectToObjectConverter());
      converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
      converterRegistry.addConverter(new FallbackObjectToStringConverter());
      if (javaUtilOptionalClassAvailable) {
          converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
      }
      }
      

      做了6件事

      1. 加入标准的转换器,如NumberToNumberConverterFactory.
      2. 加入集合的转换器,如ArrayToCollectionConverter
      3. 加入ByteBufferConverter
      4. 如果当前类路径存在java.time.ZoneId的话,加入如下转换器:

        public static void registerJsr310Converters(ConverterRegistry converterRegistry) {
        converterRegistry.addConverter(new StringToTimeZoneConverter());
        converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
        converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
        }
      5. 加入ObjectToObjectConverter,IdToEntityConverter,FallbackObjectToStringConverter
      6. 如果当前类路径存在java.util.Optional的话,加入ObjectToOptionalConverter
    3. 调用PropertiesConfigurationFactory#bindPropertiesToTarget方法.代码如下:

      public void bindPropertiesToTarget() throws BindException {
      Assert.state(this.propertySources != null, "PropertySources should not be null");
      try {
          if (logger.isTraceEnabled()) {
              logger.trace("Property Sources: " + this.propertySources);
      
          }
          this.hasBeenBound = true;
          doBindPropertiesToTarget();
      }
      catch (BindException ex) {
          if (this.exceptionIfInvalid) {
              throw ex;
          }
          PropertiesConfigurationFactory.logger
                  .error("Failed to load Properties validation bean. "
                          + "Your Properties may be invalid.", ex);
      }
      }

      3件事

      1. 首先判断propertySources是否为null,如果为null的话,抛出异常.一般不会为null的
      2. 将hasBeenBound 设为true
      3. 调用doBindPropertiesToTarget.代码如下:

        private void doBindPropertiesToTarget() throws BindException {
        RelaxedDataBinder dataBinder = (this.targetName != null
            ? new RelaxedDataBinder(this.target, this.targetName)
            : new RelaxedDataBinder(this.target));
        if (this.validator != null
            && this.validator.supports(dataBinder.getTarget().getClass())) {
        dataBinder.setValidator(this.validator);
        }
        if (this.conversionService != null) {
        dataBinder.setConversionService(this.conversionService);
        }
        dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);
        dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
        dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
        dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
        customizeBinder(dataBinder);
        Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
        Set<String> names = getNames(relaxedTargetNames);
        PropertyValues propertyValues = getPropertySourcesPropertyValues(names,
            relaxedTargetNames);
        dataBinder.bind(propertyValues);
        if (this.validator != null) {
        dataBinder.validate();
        }
        checkForBindingErrors(dataBinder);
        }

        做了7件事:

        1. 初始化RelaxedDataBinder 并进行设置一下属性.对于当前场景来说targetName = spring.main,target = SpringApplication.这样RelaxedDataBinder也就持有了SpringApplication.

          其次 由于当前的validator还是为null.因此RelaxedDataBinder的Validator不会进行设置.

          最后将PropertiesConfigurationFactory持有的转换器赋值给RelaxedDataBinder.

        2. 执行customizeBinder.这是一个扩展点,空实现

        3. 执行getRelaxedTargetNames方法,获得relaxedTargetNames,对于当前来说,其值为–>spring.main.代码如下:

          private Iterable<String> getRelaxedTargetNames() {
          return (this.target != null && StringUtils.hasLength(this.targetName)
              ? new RelaxedNames(this.targetName) : null);
          }
        4. 调用getNames.代码如下:

          private Set<String> getNames(Iterable<String> prefixes) {
          Set<String> names = new LinkedHashSet<String>();
          if (this.target != null) {
          PropertyDescriptor[] descriptors = BeanUtils
              .getPropertyDescriptors(this.target.getClass());
          for (PropertyDescriptor descriptor : descriptors) {
          String name = descriptor.getName();
          if (!name.equals("class")) {
              RelaxedNames relaxedNames = RelaxedNames.forCamelCase(name);
              if (prefixes == null) {
                  for (String relaxedName : relaxedNames) {
                      names.add(relaxedName);
                  }
              }
              else {
                  for (String prefix : prefixes) {
                      for (String relaxedName : relaxedNames) {
                          names.add(prefix + "." + relaxedName);
                          names.add(prefix + "_" + relaxedName);
                      }
                  }
              }
          }
          }
          }
          return names;
          }

          通过遍历target的属性,这里的target为SpringApplication.然后将SpringApplication的属性按照单词划分的规则,与relaxedTargetNames进行拼接

          举例说明:SpringApplication中有一个logStartupInfo属性,则拆分为log-startup-info,然后与spring.main拼接为spring.main.log-startup-info 和 spring.main_log-startup-info

          SpringApplication中持有的属性有19个,如下:

          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=addCommandLineProperties],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=additionalProfiles], 
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=applicationContextClass],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=banner],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=bannerMode],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=beanNameGenerator],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=class],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=classLoader],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=defaultProperties],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=environment],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=headless],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=initializers],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=listeners], 
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=logStartupInfo],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=mainApplicationClass],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=registerShutdownHook],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=resourceLoader],
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=sources], 
          org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=webEnvironment]
        5. 调用getPropertySourcesPropertyValues方法,生成PropertyValues.代码如下:

          private PropertyValues getPropertySourcesPropertyValues(Set<String> names,
          Iterable<String> relaxedTargetNames) {
          PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names,
              relaxedTargetNames);
          return new PropertySourcesPropertyValues(this.propertySources, names, includes,
              this.resolvePlaceholders);
          }

          调用getPropertyNamePatternsMatcher方法生成PropertyNamePatternsMatcher.由于ignoreUnknownFields等于true并且target为SpringApplication不是map的子类.因此返回的是DefaultPropertyNamePatternsMatcher.

          代码如下:

          private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names,
          Iterable<String> relaxedTargetNames) {
          if (this.ignoreUnknownFields && !isMapTarget()) {
          // Since unknown fields are ignored we can filter them out early to save
          // unnecessary calls to the PropertySource.
          return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names);
          }
          if (relaxedTargetNames != null) {
          // We can filter properties to those starting with the target name, but
          // we can't do a complete filter since we need to trigger the
          // unknown fields check
          Set<String> relaxedNames = new HashSet<String>();
          for (String relaxedTargetName : relaxedTargetNames) {
          relaxedNames.add(relaxedTargetName);
          }
          return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true,
              relaxedNames);
          }
          // Not ideal, we basically can't filter anything
          return PropertyNamePatternsMatcher.ALL;
          }

          最后直接生成PropertySourcesPropertyValues. 这个其初始化过程我们已经分析过了.不同的是.现在PropertySourcesPropertyValues 持有的PropertySources为

          [SimpleCommandLinePropertySource {name=’commandLineArgs’}, StubPropertySource {name=’servletConfigInitParams’}, StubPropertySource {name=’servletContextInitParams’}, MapPropertySource {name=’systemProperties’}, SystemEnvironmentPropertySource {name=’systemEnvironment’}, RandomValuePropertySource {name=’random’}, ConfigurationPropertySources {name=’applicationConfigurationProperties’}]

          这里比之前的新增了一个 ConfigurationPropertySources,其类图如下:

          《spring boot 源码解析4-SpringApplication#run第4步》
          由之前可知,会调用processEnumerablePropertySource方法进行处理.

        6. 调用bind,进行绑定.这里的代码我们之前已经分析过了.这里就不在赘述了.
        7. 检查在绑定过程中是否出现异常,如果有的话,抛出BindException

      经过长长的分析,ConfigFileApplicationListener的postProcessEnvironment就分析完了.不过这里有个思考.为何费这么大的劲生成一个PropertiesConfigurationFactory.这个我的想法是 由于PropertiesConfigurationFactory 实现了 FactoryBean,InitializingBean 接口.而其getObject实现如下:

      public T getObject() throws Exception {
      if (!this.hasBeenBound) {
          bindPropertiesToTarget();
      }
      return this.target;}

      因此当获取的时候,就会获得处理过后的SpringApplication

  6. AnsiOutputApplicationListener#onApplicationEvent,代码如下:

    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
                event.getEnvironment(), "spring.output.ansi.");
        if (resolver.containsProperty("enabled")) {
            String enabled = resolver.getProperty("enabled");
            AnsiOutput.setEnabled(Enum.valueOf(Enabled.class, enabled.toUpperCase()));
        }
    
        if (resolver.containsProperty("console-available")) {
            AnsiOutput.setConsoleAvailable(
                    resolver.getProperty("console-available", Boolean.class));
        }
    }
    1. 如果配置了spring.output.ansi.enabled,则调用AnsiOutput#setEnabled
    2. 如果设置了spring.output.ansi.console-available,则调用AnsiOutput#setConsoleAvailable

    由于默认情况下,我们没有配置spring.output.ansi.enabled,spring.output.ansi.console-available,因此,AnsiOutputApplicationListener相当于空操作

  7. LoggingApplicationListener#onApplicationEvent 这部分的内容请看我们的后续文章:

    1. spring boot 源码解析27-JavaLoggingSystem及LoggingSystem生命周期详解
    2. spring boot 源码解析28-Log4J2LoggingSystem
    3. spring boot 源码解析29-LogbackLoggingSystem
  8. ClasspathLoggingApplicationListener#onApplicationEvent–> 如果loger的debug级别可用的话,则打印日志:

    public void onApplicationEvent(ApplicationEvent event) {
        if (logger.isDebugEnabled()) {
            if (event instanceof ApplicationEnvironmentPreparedEvent) {
                logger.debug("Application started with classpath: " + getClasspath());
            }
            else if (event instanceof ApplicationFailedEvent) {
                logger.debug(
                        "Application failed to start with classpath: " + getClasspath());
            }
        }
    }

    默认情况下,由于logger的级别是Info,不支持debug,因此,ClasspathLoggingApplicationListener相当于空操作

  9. BackgroundPreinitializer#onApplicationEvent,最终调用了performPreinitialization方法,代码如下:

    private void performPreinitialization() {
        try {
            Thread thread = new Thread(new Runnable() {
    
                @Override
                public void run() {
                    runSafely(new MessageConverterInitializer());
                    runSafely(new MBeanFactoryInitializer());
                    runSafely(new ValidationInitializer());
                    runSafely(new JacksonInitializer());
                    runSafely(new ConversionServiceInitializer());
                    preinitializationComplete.countDown();
                }
    
                public void runSafely(Runnable runnable) {
                    try {
                        runnable.run();
                    }
                    catch (Throwable ex) {
                        // Ignore
                    }
                }
    
            }, "background-preinit");
            thread.start();
        }
        catch (Exception ex) {
            // This will fail on GAE where creating threads is prohibited. We can safely
            // continue but startup will be slightly slower as the initialization will now
            // happen on the main thread.
            preinitializationComplete.countDown();
        }
    }
    1. 调用了MessageConverterInitializer#run,代码如下:

      private static class MessageConverterInitializer implements Runnable {
      
          @Override
          public void run() {
              new AllEncompassingFormHttpMessageConverter();
          }
      }

      AllEncompassingFormHttpMessageConverter构造器如下:

      public AllEncompassingFormHttpMessageConverter() {
          addPartConverter(new SourceHttpMessageConverter<Source>());
      
          if (jaxb2Present && !jackson2XmlPresent) {
              addPartConverter(new Jaxb2RootElementHttpMessageConverter());
          }
      
          if (jackson2Present) {
              addPartConverter(new MappingJackson2HttpMessageConverter());
          }
          else if (gsonPresent) {
              addPartConverter(new GsonHttpMessageConverter());
          }
      
          if (jackson2XmlPresent) {
              addPartConverter(new MappingJackson2XmlHttpMessageConverter());
          }
      }

      添加了一系列的Converter

    2. MBeanFactoryInitializer#run,代码如下:

      private static class MBeanFactoryInitializer implements Runnable {
      
          @Override
          public void run() {
              new MBeanFactory();
          }
      }

      实例化了MBeanFactory

    3. ValidationInitializer,代码如下:

      private static class ValidationInitializer implements Runnable {
      
          @Override
          public void run() {
              Validation.byDefaultProvider().configure();
          }
      }
    4. JacksonInitializer,代码如下:

      private static class JacksonInitializer implements Runnable {
      
          @Override
          public void run() {
              Jackson2ObjectMapperBuilder.json().build();
          }
      }
    5. ConversionServiceInitializer,代码如下:

      private static class ConversionServiceInitializer implements Runnable {
      
          @Override
          public void run() {
              new DefaultFormattingConversionService();
          }
      
      }

      关于这部分的内容,都是属于spring的范畴,读者可以查阅相关资料了解,此处就不展开了

  10. DelegatingApplicationListener#onApplicationEvent,代码如下:

    if (event instanceof ApplicationEnvironmentPreparedEvent) {
            List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
                    ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
            if (delegates.isEmpty()) {
                return;
            }
            this.multicaster = new SimpleApplicationEventMulticaster();
            for (ApplicationListener<ApplicationEvent> listener : delegates) {
                this.multicaster.addApplicationListener(listener);
            }
    }

    由于默认情况下, delegates为空,因此,不会执行后续操作

  11. FileEncodingApplicationListener#onApplicationEvent,代码如下:

    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
                event.getEnvironment(), "spring.");
        if (resolver.containsProperty("mandatoryFileEncoding")) {
            String encoding = System.getProperty("file.encoding");
            String desired = resolver.getProperty("mandatoryFileEncoding");
            if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
                logger.error("System property 'file.encoding' is currently '" + encoding
                        + "'. It should be '" + desired
                        + "' (as defined in 'spring.mandatoryFileEncoding').");
                logger.error("Environment variable LANG is '" + System.getenv("LANG")
                        + "'. You could use a locale setting that matches encoding='"
                        + desired + "'.");
                logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
                        + "'. You could use a locale setting that matches encoding='"
                        + desired + "'.");
                throw new IllegalStateException(
                        "The Java Virtual Machine has not been configured to use the "
                                + "desired default character encoding (" + desired
                                + ").");
            }
        }
    }
    1. 如果配置了spring.mandatoryFileEncoding

      1. 如果配置了系统属性–>file.encoding 判断spring.mandatoryFileEncoding 是否和属性配置的不一样,如果不一样,则抛出异常

    默认情况下,spring.mandatoryFileEncoding 没有配置,因此,相当于是空操作.

至此. SpringApplication run 方法中的第4步.后续会继续分析接下来的步骤.

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/78914944
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞