SpringBoot配置文件的相关分析。我们都知道目前SpringBoot的配置文件可以配置文件很简单,支持多环境,有yml和properties,那么他的加载机制是怎样的呢?又是如何读取的?
前面提到,启动SpringBoot项目分为实例化类SpringApplication和run,在run阶段会准备环境。我们先通过代码看看环境是如何被准备的
1 2 3 4 5 6 7 8 9 10 11 12 13
| private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
|
一个从产生的对象,要将它生产出来一般都离不开创建与初始化,所以也可以将上面的分为这两部分。
创建环境
首先我们先看创建环境。getOrCreateEnvironment
会根据应用类型初始化相应的环境,条件与之前分析的创建应用上下文的相同,分为:默认,Servlet, REACTIVE。这三个环境的都会继承一个接口ConfigurableEnvironment
,我们看看接口都做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13
| void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
MutablePropertySources getPropertySources();
Map<String, Object> getSystemProperties();
Map<String, Object> getSystemEnvironment();
void merge(ConfigurableEnvironment parent);
|
可以看到他们后续的抽象类及继承类都是围绕着profile和property来工作的,前者分为默认和激活两个状态,后者用map来存储配置文件中的键值对。
配置环境
配置环境的方法是模板方法,详细分为了property
和profile
。前者是添加默认配置和命令行配置,后者是选择激活的profile。
接下来是ConfigurationPropertySources的attach。这个方法会检查特殊字段是否为空 ,然后去做对应的处理
1
| private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";
|
除了这个,我们还需要了解environment中的propertySource的类型:MutablePropertySource。他的属性是一个CopyOnWriteArrayList实现的线性表,仅此而已,返型是PropertySource对象。
下面是一个非常重要的处理,监听器的执行。之前提到,执行时会创建相应的event,然后再通过广播去找对应的listener,我们来看看listener ConfigFileApplicationListener
被触发后的执行:
1 2 3 4 5 6 7 8 9
| 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()); } }
|
响应的事件也如之前所说,都很简洁,这里环境准备的响应首先获取到后置处理器,随后将类本身也加入进去,排序后依次执行后置处理器的方法对环境做处理。
1 2 3 4
| List<EnvironmentPostProcessor> loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); }
|
1 2 3 4 5 6
| org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\ org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
|
由于listener本身也是processor(单独把它加入到processor里了),所以我们重点关注这里的后置处理。这部分也是加载配置文件的关键。
Load
后置处理也是层层委托
1 2 3 4 5
| protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| private static final Set<String> LOAD_FILTERED_PROPERTY;
static { Set<String> filteredProperties = new HashSet<>(); filteredProperties.add("spring.profiles.active"); filteredProperties.add("spring.profiles.include"); LOAD_FILTERED_PROPERTY = Collections.unmodifiableSet(filteredProperties); } void load() { FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY, (defaultProperties) -> { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); initializeProfiles(); while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); if (isDefaultProfile(profile)) { addProfileToEnvironment(profile.getName()); } load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); addLoadedPropertySources(); applyActiveProfiles(defaultProperties); });
|
上面的方法看着长,别被lambda吓到了,实际上是调用了apply方法,后面那个只是一个consumer参数。也就是在这个listener里解析了properties以及yml,定义了解析顺序和规则。里面有多个重载的load方法,在load方法内还会遍历地用propertySourceLoaders中的loader去调用load方法,propertySourceLoader是一个接口,有两个实现类:PropertiesPropertySourceLoader
和YamlPropertySourceLoader
。
自定义
我们知道,加载了postprocessor后会触发processor,如果要自定义配置文件或者指定路径呢?利用他的SPI机制,我们可以在在META-INF下创建spring.factories,在里面按照给定的key加上我们实现了EnvironmentPostProcessor
的类。可以参考给出的类来写,将配置文件加入列表即可。