所有⽂章
触发加载配置⽂件
在中,我们看到了Environment对象的创建⽅法。同时也稍微提及了⼀下ConfigFileApplicationListener这个,这个主要⼯作是为了加载application.properties/yml配置⽂件的。回顾⼀下prepareEnvironment⽅法的代码
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments ) {
// 创建⼀个Environment对象
ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置Environment对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 触发(主要是触发ConfigFileApplicationListener,这个将会加载如application.properties/yml这样的配置⽂件) listeners.environmentPrepared(environment); // 省略}
我们看到Environment对象在初始创建并配置之后会发布出⼀个事件给,注意!这⾥的并不是ConfigFileApplicationListener⽽是⼀个负责分发事件的EventPublishingRunListener。
我们跟进EventPublishingRunListener的environmentPrepared⽅法
private final SimpleApplicationEventMulticaster initialMulticaster;
@Override
public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}
这⾥包装了⼀个ApplicationEnvironmentPreparedEvent事件,并通过⼴播的⽅式⼴播给监听该事件的,到这个时候才触发了ConfigFileApplicationListener
我们跟进ConfigFileApplicationListener的onApplicationEvent⽅法
@Override
public void onApplicationEvent(ApplicationEvent event) { // 只触发Environment相关的事件
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); }
if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); }}
Event将会触发onApplicationEnvironmentPreparedEvent 继续跟进
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { List AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { // 执⾏后置处理器 postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); }} 我们看到,⾸先加载了Environment的后置处理器,然后经过排序以后遍历触发每个处理器。这⾥注意,ConfigFileApplicationListener本⾝也实现了EnvironmentPostProcessor接⼝,所以这⾥将会触发ConfigFileApplicationListener内部⽅法执⾏ 我们跟进ConfigFileApplicationListener的postProcessEnvironment⽅法 @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader());} 再跟进addPropertySources⽅法 protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load();} 我们看到,这⾥实例化了⼀个Loader⽤来加载application配置⽂件,⽽核⼼逻辑就在load⽅法当中。 加载器加载application配置⽂件 跟进Loader加载器的构造⽅法中 Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment); this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); // ⽂件application配置⽂件的资源加载器,包括propertis/xml/yml/yaml扩展名 this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());} 我们注意到,再构造⽅法中将会从spring.factories中加载PropertySourceLoader接⼝的具体实现类,具体请参阅:。我们打开spring.factories可以看到 这⾥包括两个实现 1)PropertiesPropertySourceLoader:⽤于加载property/xml格式的配置⽂件2) YamlPropertySourceLoader:⽤于加载yml/yaml格式的配置⽂件 到这⾥,我们可以知道springboot⽀持的不同配置⽂件是通过选择不同的加载器来实现 下⾯,我们回到Loader加载器的load⽅法中,跟进加载的主要逻辑 public void load() { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles(); while (!this.profiles.isEmpty()) { // 消费⼀个profile Profile profile = this.profiles.poll(); // active的profile添加到Environment if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } // 加载 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 重置Environment中的profiles resetEnvironmentProfiles(this.processedProfiles); // 加载 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();} 代码有点⼩长,我们根据如何加载默认的application.properties/yml配置⽂件的流程来了解⼀下 先跟进initializeProfiles⽅法看看如果初始化profiles private void initializeProfiles() { // 第⼀个profile为null,这样能保证⾸个加载application.properties/yml this.profiles.add(null); Set for (String defaultProfileName : this.environment.getDefaultProfiles()) { Profile defaultProfile = new Profile(defaultProfileName, true); this.profiles.add(defaultProfile); } }} 这⾥注意两点 1)将会⾸先添加⼀个null,保证第⼀次加载的是application配置 2) 其次,如果没有配置profile,那么使⽤default。注意,我们的application配置⽂件还未加载,所以这⾥的\"没有配置\"并不是指你的application配置⽂件中有没有配置,⽽是如命令⾏、获取main⽅法传⼊等其它⽅法配置我们并未配置任何active的profile,所以这⾥最终将产⽣⼀个这样的数据profiles=[null, \"default\"] 回到load⽅法中,我们继续往下看 public void load() { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles(); while (!this.profiles.isEmpty()) { // 消费⼀个profile Profile profile = this.profiles.poll(); // active的profile添加到Environment if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } // 加载 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 重置Environment中的profiles resetEnvironmentProfiles(this.processedProfiles); // 加载 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();} while循环中,⾸先拿到的是profile=null,然后就直接进⼊第⼆个load加载⽅法加载配置⽂件 我们跟进第⼆个load加载⽅法(请注意区分load⽅法,后续还会出现load⽅法,我们以出现的顺序区分) private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { // 获取并遍历所有待搜索的位置 getSearchLocations().forEach((location) -> { boolean isFolder = location.endsWith(\"/\"); // 获取所有待加载的配置⽂件名 Set names.forEach((name) -> load(location, name, profile, filterFactory, consumer)); });} 该⽅法中的逻辑主要是搜索每个位置下的每个指定的配置⽂件名,并加载 跟进getSearchLocations看看要搜索哪些位置 private static final String DEFAULT_SEARCH_LOCATIONS = \"classpath:/,classpath:/config/,file:./,file:./config/\";private Set if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(CONFIG_LOCATION_PROPERTY); } Set asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations;} 很显然,由于我们没有⾃定义⼀些搜索位置,那么默认搜索classpath:/、classpath:/config/、file:./、file:./下 回到第⼆个load⽅法,我们再看看getSearchNames⽅法要加载哪些⽂件 private static final String DEFAULT_NAMES = \"application\"; private Set if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { String property = this.environment.getProperty(CONFIG_NAME_PROPERTY); return asResolvedSet(property, null); } return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);} 相似的逻辑,最终返回默认的配置⽂件名application,也就是我们最熟悉的名字 接下来,再回到第⼆个load⽅法,我们可以跟进第三个load⽅法了,看看如何根据locations和names来加载配置⽂件 private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { // 省略 Set for (PropertySourceLoader loader : this.propertySourceLoaders) { // 获取扩展名 for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { // 加载对应扩展名的⽂件 loadForFileExtension(loader, location + name, \".\" + fileExtension, profile, filterFactory, consumer); } } }} 这个load⽅法主要逻辑表明将会加载每个加载器可以⽀持的配置⽂件,在Loader初始化的时候我们获得了两个加载器,同时每个加载器⽀持两种格式。所以这⾥的嵌套遍历中,我们将会尝试加载4种配置⽂件,如1)application.properties2) application.xml3) application.yml4) application.yaml 再跟进loadForFileExtension⽅法,看看具体每种的加载 private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null); DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile); // 当前没有profile if (profile != null) { String profileSpecificFile = prefix + \"-\" + profile + fileExtension; load(loader, profileSpecificFile, profile, defaultFilter, consumer); load(loader, profileSpecificFile, profile, profileFilter, consumer); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) { String previouslyLoaded = prefix + \"-\" + processedProfile + fileExtension; load(loader, previouslyLoaded, profile, profileFilter, consumer); } } } // 加载具体格式的⽂件 load(loader, prefix + fileExtension, profile, profileFilter, consumer);} 由于当前profile=null,所以我们直接进⼊第四个load⽅法 跟进第四个load⽅法,由于该⽅法有点长,我们省略次要的代码 private void load( PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, DocumentConsumer consumer ) { try { // 获取资源 Resource resource = this.resourceLoader.getResource(location); // 加载为Document对象 List List for (Document document : documents) { if (filter.match(document)) { // 添加profile addActiveProfiles(document.getActiveProfiles()); addIncludedProfiles(document.getIncludeProfiles()); loaded.add(document); } } Collections.reverse(loaded); if (!loaded.isEmpty()) { // 回调处理每个document loaded.forEach((document) -> consumer.accept(profile, document)); } } catch (Exception ex) {}} ⾸先配置⽂件会被加载为Document这样的内存对象,并最终回调处理。 这⾥我们看到回调是调⽤consumer这样⼀个接⼝,我们得回到第⼀个load⽅法,看看调⽤第⼆个load⽅法的时候传⼊的consumer是啥 public void load() { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles(); while (!this.profiles.isEmpty()) { // 消费⼀个profile Profile profile = this.profiles.poll(); // active的profile添加到Environment if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } // 加载 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 重置Environment中的profiles resetEnvironmentProfiles(this.processedProfiles); // 加载 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();} 我们看到,在调⽤第⼆个load⽅法的时候就通过addToLoaded这个⽅法的执⾏来获取⼀个consumer,⽤来回调处理配置⽂件的Document对象。 注意!在调⽤addToLoaded的时候通过⽅法引⽤指定了⼀个method,这个method将在consumer回调的内部被使⽤。method=MutablePropertySources:addLast 跟进addToLoaded⽅法 private Map private DocumentConsumer addToLoaded( BiConsumer MutablePropertySources merged = this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources()); // 回调method addMethod.accept(merged, document.getPropertySource()); };} 我们看到,loaded是⼀个profile和MutableProperySources的键值组合。⽅法逻辑中将会先获取loaded⾥⾯的MutablePropertySources,然后调⽤addLast⽅法将Document中的PropertySource给添加到MutablePropertySources中。 到这⾥,⼀个application配置⽂件被加载到内存了。但是还没完,前⾯的⽂章中我们说过Environment对象是应⽤程序环境的抽象,包含了properties。那么,我们还得将这些内存中的PropertySource给添加到Environment中。 回到第⼀个load⽅法中 public void load() { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles(); while (!this.profiles.isEmpty()) { // 消费⼀个profile Profile profile = this.profiles.poll(); // active的profile添加到Environment if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } // 加载 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 重置Environment中的profiles resetEnvironmentProfiles(this.processedProfiles); // 加载 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();} 我们看到最后⼀⾏,addLoadedPropertySources⽅法的作⽤也就是将之前loaded⾥⾯的东西给添加到Environment中 跟进addLoadedPropertySources⽅法 private void addLoadedPropertySources() { MutablePropertySources destination = this.environment.getPropertySources(); List Collections.reverse(loaded); String lastAdded = null; Set for (MutablePropertySources sources : loaded) { // 遍历配置 for (PropertySource> source : sources) { // 排重 if (added.add(source.getName())) { // 添加每个到Environment中 addLoadedPropertySource(destination, lastAdded, source); lastAdded = source.getName(); } } }} 这个⽅法很清晰地表明,将loaded中地PropertySource给追加到Environment中。 到这⾥,默认的application.properties/yml这样地配置⽂件就被加载到了Environment当中了。不过还没有结束,这⾥还有个⽐较重要的问题,多环境的时候application.properties/yml配置⽂件中指定了profile,就会加载application-{profile}.properties/yml是怎么实现的呢? 加载多环境的application-{profile}配置⽂件 回到我们之前的第四个load⽅法 private void load( PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, DocumentConsumer consumer ) { try { // 获取资源 Resource resource = this.resourceLoader.getResource(location); // 加载为Document对象 List List for (Document document : documents) { if (filter.match(document)) { // 添加profile addActiveProfiles(document.getActiveProfiles()); addIncludedProfiles(document.getIncludeProfiles()); loaded.add(document); } } Collections.reverse(loaded); if (!loaded.isEmpty()) { // 回调处理每个document loaded.forEach((document) -> consumer.accept(profile, document)); } } catch (Exception ex) {}} 这⾥,已经把默认的application.properties/yml给加载成为了Document。然后在遍历documents的时候,会把Document中的profiles做⼀次添加 我们跟进addActiveProfiles看看 private Deque void addActiveProfiles(Set // 省略 // 添加到队列 this.profiles.addAll(profiles); // 省略 this.activatedProfiles = true; // 移除掉default removeUnprocessedDefaultProfiles();} 我们看到,新的profiles⾸先会被添加到现有队列中。最初的profiles=[null, \"default\"]。⽽后,我们消费了null,profiles=[\"default\"]。现在,我们添加⼀个profile=\"test\"。那么,profiles=[\"default\。 再看最后⼀⾏removeUnprocessedDefaultProfiles,将会移除default。所以,最终profiles=[\"test\"]。 再回到第⼀个load⽅法中 public void load() { this.profiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); // 初始化profiles initializeProfiles(); while (!this.profiles.isEmpty()) { // 消费⼀个profile Profile profile = this.profiles.poll(); // active的profile添加到Environment if (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } // 加载 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } // 重置Environment中的profiles resetEnvironmentProfiles(this.processedProfiles); // 加载 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); // 添加所有properties到Environment中 addLoadedPropertySources();} 这时候while循环⾥⾯将会拿到profile=\"test\",跟之前⼀样⼀路下去,直到loadForFileExtension⽅法 跟进loadForFileExtension private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null); DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile); if (profile != null) { // 拼接如:application-test.properties/yml String profileSpecificFile = prefix + \"-\" + profile + fileExtension; // 加载⽂件 load(loader, profileSpecificFile, profile, defaultFilter, consumer); load(loader, profileSpecificFile, profile, profileFilter, consumer); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) { String previouslyLoaded = prefix + \"-\" + processedProfile + fileExtension; load(loader, previouslyLoaded, profile, profileFilter, consumer); } } } // 加载具体格式的⽂件 load(loader, prefix + fileExtension, profile, profileFilter, consumer);} 我们看到,当有profile的时候⽂件名就不再是application.properties/yml了。它会把profile给拼接上去,所以就变成了application-test.properties/yml,并加载⽂件。后续也⼀样得最终添加到Environment当中。 总结 application配置⽂件的加载过程逻辑并不复杂,只是具体细节⽐较多,所以代码中包含了不少附加的逻辑。那么抛开细节,我们可以看到其实就是到相应的⽬录下搜索相应的⽂件是否存在,加载到内存以后再添加到Environment当中。 ⾄于具体的细节如:加载⽂件的时候编码相关、多个⽂件相同配置是否覆盖、加载器如何解析各种配置⽂件的内容有时间也可以仔细阅读。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- sarr.cn 版权所有 赣ICP备2024042794号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务