1、简单使用
我们知道spring boot 默认会加载appication.properties配置文件,我们在此配置文件中配置一个name熟悉,然后写一个rest接口进行获取,代码如下:
application.properties配置文件中:
server.port=8080
name=wen default
Controller:
@RestController
public class TestController {
@Value("${name}")
private String name;
@RequestMapping("name")
public String name(){
return name;
}
}
2、各种配置方式总结:
Program arguments : 配置形式:--name=wen-ProgramArg 在环境类中存放的配置源名称:commandLineArgs 优先级:第1
VM options : 配置形式:-Dname=wen-vm 在环境类中存放的配置源名称:systemProperties 优先级:第4
application-{profile}.properties 配置形式:name=wen-test 在环境类中存放的配置源名称:applicationConfig: [classpath:/application-{profile}.properties] 优先级:倒数第2 如果一次激活多个,激活的顺序表示优先级,
比如:--spring.profiles.active=dev,test 将会添加两个配资源,
test优先级高于dev
application.properties 配置形式:name=wen-def 在环境类中存放的配置源名称:applicationConfig: [classpath:/application.properties] 优先级:倒数第1
注意点1:application.properties可以不需要,但是一定需要指定profile的文件:
案例:step1:删除application.properties 文件。
step2:必须使用系统属性-D 或者使用应用参数-- 来指定profile,如 --spring.profiles.active=dev
step3:此时我们就需要添加application-dev.properties 配置文件完成配置。
注意点2:使用系统变量-D 或 应用参数-- 指定了profile的时候,然后我们在配置文件中又指定激活的profile的时候将会无效,
因为spring boot 不支持已经激活了profile 后又再次激活profile。源码中的原话是"Profiles already activated, '" + profiles + "' will not be applied"。
这种情况还有一种情况,比如我们没有使用命令行的形式指定profile,这个时候默认会加载application.properties,如果我们在application.properties中
配置了profile=dev,然后我们在application-dev.properties中又配置了profile=test,这个时候不会激活tes环境,理由跟上面一样,不许再次激活profile,
不管你激活的是否是同一个profile,都是不行的。
spring.profiles.incloude:表示需要引入的子项配置:
比如有的环境我们需要使用mybatis配置,有的环境不需要使用mybatis配置,那就可以使用spring.profiles.incloude来进行配置的子项映入,
案例:--spring.profiles.incloude=mybatis 那么application-mybatis.properties 也将会以profile被激活。
看不全图片显示:
3、ConfigFileApplicationListener配置文件监听器源码在整应用上下文生命周期的执行时机分析:
ConfigFileApplicationListener这个监听器是专门负责根据配置去加载配置文件,当我们不做任何配置的时候,ConfigFileApplicationListener会读取"classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/"等路径下,默认名称是application,后缀名是properties或yml的配置文件,然后将其解析成为一个配置源PropertySource然后添加到环境实例environment中,且添加的顺序是放在最后一位表示优先级最低。
3.1、ConfigFileApplicationListener的执行时机:
ConfigFileApplicationListener是一个监听器,了解spring的事件机制才能看懂它的实现方式,既然是事件监听器那么肯定会有自己监听是事件,我们来看一下源码,看看ConfigFileApplicationListener都监听了那些事件:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
由此可以看出来ConfigFileApplicationListener监听了所有的事件,但是只处理两种事件就是:
1、ApplicationEnvironmentPreparedEvent :应用环境准备完成事件。
2、ApplicationPreparedEvent:应用准备完成事件。
3.2、ApplicationEnvironmentPreparedEvent是在什么时候发布的呢?
ApplicationEnvironmentPreparedEvent事件是在spring 应用的环境实例environment准备完车后发布的,源码实现如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
使用SpringApplicationRunListeners监听器来进行环境准备完成处理,
SpringApplicationRunListener监听器区别于ApplicationListener,ApplicationListener负责处理事件
event,而SpringApplicationRunListener监听器不监听事件,而是在spring应用上下文的生命周期中进行一
些通知,并不是基于事件来通知。
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
void environmentPrepared(ConfigurableEnvironment environment) {
找到所有的SpringApplicationRunListener实例来进行调用,这里的listeners是通过SPI获取的,
默认只有一个EventPublishingRunListener实例,这个EventPublishingRunListener负责事件发布与执行监
听器。
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
加下来我们来到EventPublishingRunListener的environmentPrepared(ConfigurableEnvironment environment):
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
使用一个事件广播器广播一个ApplicationEnvironmentPreparedEvent应用环境准备好的事件出去,
这样我们的ConfigFileApplicationListener就能监听到这个事件,ConfigFileApplicationListener是同步
执行的,应为我们的事件广播器并没有配置一个线程池,所以是同步执行的。
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
而我们的ConfigFileApplicationListener是什么时候实例化的呢?在项目启动阶段就会通过SPI的方式直接获取到ConfigFileApplicationListener的实例然后添加到应用 山下文中,然后就来到了ConfigFileApplicationListener的onApplicationEvent(ApplicationEvent event)方法了。
4、ConfigFileApplicationListener处理ApplicationEnvironmentPreparedEvent应用环境准备完成事件的原理:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
处理应用上下文的环境准备完成事件
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
1、先获取环境后置处理器列表,获取方式也是使用SPI。
List postProcessors = loadPostProcessors();
2、将当前的ConfigFileApplicationListener也加入到环境后置处理器中,因为
ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口,所以也是一个环境后置处理器。
postProcessors.add(this);
3、将环境后置处理器进行排序。
AnnotationAwareOrderComparator.sort(postProcessors);
4、调用所有的环境后置处理器,我们就讲解ConfigFileApplicationListener这个环境后置处理器
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
ConfigFileApplicationListener即是一个监听器也是一个环境后置处理器,其处理器上下文环境的实现如下:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
添加配资源,可能会添加多个,到这一步的时候,环境类中肯定已经存在如下配置源了:
commandLineArgument
如果是环境下还会有servlet的两个配置源
systemProperties
systemEnvironment
addPropertySources(environment, application.getResourceLoader());
}
添加配资源的实现:
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
1、先添加一个随机值的配资源,这个就是专门解析一些spring表达式的,如配置server.port=${random.int}
RandomValuePropertySource.addToEnvironment(environment);
2、构建一个内部类Loader实例,然后加载。
new Loader(environment, resourceLoader).load();
}
Loader的构造函数:
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
1、将Loader实例的environment属性设置为之前准备好的环境实例
this.environment = environment;
2、将当前的占位符解析器设置为new的一个PropertySourcesPlaceholdersResolver实例。
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
3、设置当前Loader实例的资源载入器。
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
4、使用SPI的方式将当前Loader实例的配置源载入器列表进行设置。
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
Loader实例的load()方法实现:
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
1、创建一个空的集合赋值到当前Loader实例的双端队列属性profiles,表示当
前激活的profile列表。
this.profiles = new LinkedList<>();
2、创建一个空集合赋值到当前Loader实例的已经处理好的profile列表。
this.processedProfiles = new LinkedList<>();
3、当前Loader实例的activatedProfiles这个参数很重要,它决定了当前应用
上下文已经激活了profile还能否再次被激活,这个会决定我们的使用。
this.activatedProfiles = false;
4、loaded 属性表示是被加载的属性源,结构是Map,key=profile
value=MutablePropertySources
this.loaded = new LinkedHashMap<>();
5、初始化profile,这里会获取到在这里之前激活的profile列表,然后赋值到
当前Loader实例的profiles中。
initializeProfiles();
6、如果激活的profile列表不为空,那就进行循环配资源载入处理,这里就是
spring boot支持多环境配置的实现。
while (!this.profiles.isEmpty()) {
7、profiles是双端队列,处理每一个profile之前,都会先将其移除队
列,然后再载入这个profile对应的配置文件,从而得到一个当前
profile对应的配置源。
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
8、载入当前profile对应的配置源,然后将其添加到当前Loader实例的
loaded map中。
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
9、将已经载入的profile对应的配置源添加到环境实例中。
addLoadedPropertySources();
10、然后修改环境中的激活profile列表。
applyActiveProfiles(defaultProperties);
});
}
初始化profile 实现:
private void initializeProfiles() {
// The default profile for these purposes is represented as null. We add it
// first so that it is processed first and has lowest priority.
1、先添加一个null到当前Loader实例的profiles中,这个null profile对应的资源文件就是application.properties\yml这就是spring boot 会默认载入名称是application的资源文件的原因。
this.profiles.add(null);
Binder binder = Binder.get(this.environment);
2、获取在环境实例准备阶段激活的profile,比如通过命令行激活的profile。
Set activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);
3、获取在环境实例准备阶段激活的配置子项include,比如通过命令行激活的include。
Set includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);
4、在获取其他激活的profile,比如我们通过代码方式设置的profile,为什么需要这一步呢,
那是因为2、3步骤都只是通过spring.profiles.active、spring.profiles.include去获取profile,而不
是直接从环境实例中获取激活的profile列表,因为环境实例中获取激活的profile列表可能会包含我们通过
code的方式添加的profile。
List otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
5、将获取到其他方式激活的profile添加到当前Loader实例的双端队列profiles中。
this.profiles.addAll(otherActiveProfiles);
// Any pre-existing active profiles set via property sources (e.g.
// System properties) take precedence over those added in config files.
6、将通过使用配置项名称=spring.profiles.include获取到的配置子项添加到当前Loader实
例的双端队列profiles中。
this.profiles.addAll(includedViaProperty);
7、使用通过配置项名称=spring.profiles.active获取到的激活profile添加到当前Loader实例的双端队列profiles中,这里很重要,情况如下:
1、spring.profiles.active激活的环境不为空,那就添加到当前Loader实例
的双端队列profiles中,并且会将activatedProfiles属性修改为true,改
为true就表示就算在载入配置文件的时候,配置文件中也配置了
spring.profiles.active的话,那么将不会被激活。
2、spring.profiles.active激活的环境是空,那就不做任何事情,直接返回,
这个时候activatedProfiles属性还是false,那么当载入配置文件的时候,
配置文件中也配置了spring.profiles.active的话,就会被激活。
addActiveProfiles(activatedViaProperty);
8、如果最后发现当前的Loader实例的profiles只有一个,那就获取环境中默认的profile列表,然后构建成默认的profile添加到Loader实例的profiles中。
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
在前面我们说了配置方式总结,这些规则完全是在源码中有体现的,ConfigFileApplicationListener 的原理就是这样,其实发现不算复杂,但是很重要,因为在开发过程中,如何正确的进行项目配置是一个基本功。