默认配置文件

springboot默认会自动加载配置文件application.properties或application.yml文件。在springboot启动时,依次会从以下目录进行查找配置文件:

1、classpath

首先从classpath目录下查找配置文件,然后从 classpath:/config目录下查找配置文件

2、当前目录

首先从当前目录下查找,然后从./config目录下查找

启动时会按照上面的顺序进行加载配置文件,如果存在多个,后面的会覆盖前面的配置项值。读取的配置项参数会封装成一个PropertySources 对象存储到spring的environment中。environment在springboot中被暴露成一个bean,可以直接在需要地方注入使用。

配置文件名:

默认情况下配置文件的名称为application,如果不想使用该文件名,可以通过spring.config.name参数来指定具体的文件名,一般也没人会改吧。同样的你如果不想从上面默认位置加载配置文件,也可以通过spring.config.location参数来指定配置文件位置,多个配置文件用英文逗号相隔。

如下配置:

java -jar boot.jar -Dspring.config.location=optional:classpath:/default.properties,optional:classpath:/extend.properties

这里的optional意思可选的意思,当文件不存在时不会报错。spring.config.location可以指定配置时一个目录,如果是目录要以/结尾。

spring.config.name和spring.config.location两个配置项都是在读取配置文件前来确定文件名和文件位置,因此需要配置在启动参数里,或者系统环境变量。environment默认会对其这些配置。

额外配置文件

如果有多个配置文件,只是根据spring.config.name读取一个配置文件无法满足要求,可以使用import来引入额外的配置文件

spring.config.import=optional:classpath:/ext/extend.properties

这个配置可以配置在application配置文件中,多个用英文逗号隔开。

profile特定配置文件

springboot在加载完默认配置文件和可能的import文件后,还会根据当前配置的profile值进行读取配置文件,读取文件名application-{profile}。文件位置还是和上面的一致。可以通过spring.profiles.active来指定profile。可以指定多个用英文逗号隔开,默认值是default。也就是在不配置的情况下,会尝试读取配置文件application-default。

如果这里指定spring.profiles.active=dev,则会读取application-dev配置文件。spring.profiles.active的值即可以通过启动参数指定,也可以通过在application配置文件中指定。

值得主要的是对应额外import的文件,也会尝试按profile进行读取。如上面配置的import文件/ext/extend.properties,则会尝试读取/ext/extend-dev.properties

源码分析

1、SpringApplication初始化

SpringApplication类有两个属性

private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;

在SpringApplication的构造函数会通过SpringFactoriesLoader根据配置进行加载

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

listeners有8个,其中一个是EnvironmentPostProcessorApplicationListener。

然后调用SpringApplication.run()方法。

会通过SpringFactoriesLoader加载SpringApplicationRunListener

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

获取的runListener实例是EventPublishingRunListener类型。

EventPublishingRunListener创建的时候会调用其有参构造函数

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

这里就将最开始SpringApplication初始化的8哥listeners传给了EventMulticaster。看到这里就知道会使用EventMulticaster来发布事件,不知道spring事件通知机制的可以看下前面的一篇文章。

spring event事件通知机制

2、初始化environment,发布事件

再往下开始初始化environment,首先创建一个environment。这时候environment里只有系统变量和启动参数变量等基本信息。

SpringApplication#prepareEnvironment

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 = convertEnvironment(environment);
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

environment创建完成后会通过listeners.environmentPrepared方法来发布环境就绪事件,这里listners只有一个EventPublishingRunListener类型实例,其environmentPrepared方法会通过EventMulticaster来发布ApplicationEnvironmentPreparedEvent事件,事件发布后会上面初始化的8个 EventMulticaster的listeners其中之一EnvironmentPostProcessorApplicationListener会接收到事件,会在其onApplicationEnvironmentPreparedEvent()方法处理事件,处理事件是会通过SpringFactoriesLoader来加载EnvironmentPostProcessor类型的processor,逐个调用其postProcessEnvironment()方法,其中一个是ConfigDataEnvironmentPostProcessor,这个是主要来处理配置文件的processor。

3、配置文件读取

ConfigDataEnvironmentPostProcessor会使用ConfigDataEnvironment来处理配置文件读取。

来看下ConfigDataEnvironment的静态初始化块

static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
static {
   List<ConfigDataLocation> locations = new ArrayList<>();
   locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
   locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
   DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
}

DEFAULT_SEARCH_LOCATIONS是配置文件默认搜索位置,看到这里就能明白配置文件的读取顺序了吧。像前面提到的spring.config.location和spring.config.import配置在该类里都有对应的变量来去读取。

ConfigDataEnvironment创建时候会将envirmoent对象传进来,读取配置文件通过processAndApply()方法。

void processAndApply() {
   ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
         this.loaders);
   registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
   ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
   ConfigDataActivationContext activationContext = createActivationContext(
         contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
   contributors = processWithoutProfiles(contributors, importer, activationContext);
   activationContext = withProfiles(contributors, activationContext);
   contributors = processWithProfiles(contributors, importer, activationContext);
   applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
         importer.getOptionalLocations());
}

这里看到先是通过processWithoutProfiles方法解析不带profiles的配置文件,然后通过processWithProfiles解析带profiles的配置文件,最后将解析到的配置文件设置到environment中。再往下的代码就不看了。

提以下最后配置文件的解析,这里又是通过SpringFactoriesLoader来加载PropertySourceLoader类型的配置文件解析类。这里配置有两个PropertiesPropertySourceLoader和YamlPropertySourceLoader。一个用来解析properties文件,一个用来解析YamlPropertySourceLoader配置文件。