默认配置文件
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配置文件。