springboot 读取application.properties流程
一、application.properties配置如下,當(dāng)然也可以配置YAML。
application-dev.properties
server.port=8110 spring.application.name=newday-servicespring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=truelogging.level.org.hibernate.type.descriptor.sql.BasicBinder=tracespring.datasource.primary.jdbc-url=jdbc:mysql://rm-wz96s5izji5099uz13o.mysql.rds.aliyuncs.com:3306/xfz178_com?serverTimezone=UTC&characterEncoding=utf8## test2 database #spring.datasource.localdb.jdbc-url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8 #spring.datasource.localdb.username=root #spring.datasource.localdb.password=123456 #spring.datasource.localdb.driverClassName=com.mysql.cj.jdbc.Driver#logging.file.name=./logs/newday-service.log #日志級別 #logging.file.level.ROOT=DEBUG #logging.config=classpath:log4j2.xml logging.config=classpath:logback-spring.xmlredisson.address = redis://127.0.0.1:6379 redisson.password =#people.beanfactory = PeopleSpringFactory people.beanfactory = PeopleBeanFactory?二、加載流程
? ?1.SpringApplication在Run時,會先預(yù)加載環(huán)境,即是讀取各種配置到環(huán)境對象中,然后會廣播
ApplicationEnvironmentPreparedEvent消息。 org.springframework.boot.context.event.environmentPrepared @Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}2.org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEvent會被觸發(fā)調(diào)用,因為其實現(xiàn)了ApplicationListener會接收事件。
public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}if (event instanceof ApplicationPreparedEvent) {onApplicationPreparedEvent(event);}}?3.然后會調(diào)用到ConfigFileApplicationListener.postProcessEnvironment,去加載屬性配置文件到環(huán)境對象。
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {addPropertySources(environment, application.getResourceLoader());}protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {RandomValuePropertySource.addToEnvironment(environment);new Loader(environment, resourceLoader).load();}4.在ConfigFileApplicationListener.Loader的內(nèi)部類中,會初始化兩個資源配置文件加載器。properties,yaml
?5.在loader.load方法中會初始化所有的profile,然后再搜索可用的PROFILE配置文件。
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);});}6.?第二個load方法
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {getSearchLocations().forEach((location) -> {boolean isDirectory = location.endsWith("/");Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;names.forEach((name) -> load(location, name, profile, filterFactory, consumer));});}最終讀取到的搜索路徑為:
?獲取搜索文件名,這里注意,如果環(huán)境對象中有CONFIG_NAME_PROPERT(spring.config.name)這個配置屬性,前面的bootStrap.properties就是通過前一個BootstrapApplicationListener加載的listener,這個對象會在監(jiān)聽到環(huán)境準(zhǔn)備事件時,設(shè)置spring.config.name為bootStrap,然后由當(dāng)前這個listener來加載配置,則搜索這個文件名,否則搜索默認(rèn)文件名DEFAULT_NAMES(application):
private Set<String> getSearchNames() {if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);Set<String> names = asResolvedSet(property, null);names.forEach(this::assertValidConfigName);return names;}return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);}7.接著調(diào)用第三個load方法,搜索不同目錄下的配置文件。
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,DocumentConsumer consumer) {Set<String> processed = new HashSet<>();for (PropertySourceLoader loader : this.propertySourceLoaders) {for (String fileExtension : loader.getFileExtensions()) {if (processed.add(fileExtension)) {loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,consumer);}}}}8.接著調(diào)用第四個load,加載指定擴(kuò)展名的文件。
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) {// Try profile-specific file & profile section in profile file (gh-340)String profileSpecificFile = prefix + "-" + profile + fileExtension;load(loader, profileSpecificFile, profile, defaultFilter, consumer);load(loader, profileSpecificFile, profile, profileFilter, consumer);// Try profile specific sections in files we've already processedfor (Profile processedProfile : this.processedProfiles) {if (processedProfile != null) {String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;load(loader, previouslyLoaded, profile, profileFilter, consumer);}}}// Also try the profile-specific section (if any) of the normal fileload(loader, prefix + fileExtension, profile, profileFilter, consumer);}9.接著調(diào)用第五個load方法,真正去目錄搜索指定文件名和后綴的文件,然后加載到文檔。
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,DocumentConsumer consumer) {Resource[] resources = getResources(location);for (Resource resource : resources) {try {String name = "applicationConfig: [" + getLocationName(location, resource) + "]";List<Document> documents = loadDocuments(loader, name, resource);List<Document> loaded = new ArrayList<>();for (Document document : documents) {if (filter.match(document)) {addActiveProfiles(document.getActiveProfiles());addIncludedProfiles(document.getIncludeProfiles());loaded.add(document);}}Collections.reverse(loaded);if (!loaded.isEmpty()) {loaded.forEach((document) -> consumer.accept(profile, document));if (this.logger.isDebugEnabled()) {StringBuilder description = getDescription("Loaded config file ", location, resource,profile);this.logger.debug(description);}}}}}10.在加載文檔時最后會轉(zhuǎn)換文檔 。
private List<Document> asDocuments(List<PropertySource<?>> loaded) {if (loaded == null) {return Collections.emptyList();}return loaded.stream().map((propertySource) -> {Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),this.placeholdersResolver);String[] profiles = binder.bind("spring.profiles", STRING_ARRAY).orElse(null);Set<Profile> activeProfiles = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);Set<Profile> includeProfiles = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);return new Document(propertySource, profiles, activeProfiles, includeProfiles);}).collect(Collectors.toList());}11.getProfiles(binder, ACTIVE_PROFILES_PROPERTY)這句話,會去從當(dāng)前讀取到的配置文件屬性中查找spring.profiles.active屬性,然后設(shè)置到環(huán)境變量。
? ? ? ? 這個綁定方法和@configurationProperties中的對象屬性綁定原理一致,都是從資源屬性列表中逐個資源文件中搜索名稱為name的配置。像這里就是spring.profile.active,spring.profile.include
?注意這里的binder不是當(dāng)前環(huán)境變量的binder,而是當(dāng)前搜索到的配置文件動態(tài)生成的BINDER.
Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),this.placeholdersResolver);?現(xiàn)在我們讀取application.properties的配置數(shù)據(jù)了,只有一個。
?12.然后我們接著最后一個load代碼,當(dāng)讀取到默認(rèn)的配置文檔后,會再次加載激活和包含的profile配置。
addActiveProfiles(document.getActiveProfiles());addIncludedProfiles(document.getIncludeProfiles());loaded.add(document); void addActiveProfiles(Set<Profile> profiles) {this.profiles.addAll(profiles);this.activatedProfiles = true;removeUnprocessedDefaultProfiles();}此時我們可以看到,已經(jīng)把dev的profile也加進(jìn)來了。
?13.最后會把加載到的文檔 配置屬性回調(diào)給消費者函數(shù)。
loaded.forEach((document) -> consumer.accept(profile, document)); ConfigFileApplicationListener.addToLoaded就是消費的回調(diào)。 private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,boolean checkForExisting) {return (profile, document) -> {if (checkForExisting) {for (MutablePropertySources merged : this.loaded.values()) {if (merged.contains(document.getPropertySource().getName())) {return;}}}MutablePropertySources merged = this.loaded.computeIfAbsent(profile,(k) -> new MutablePropertySources());addMethod.accept(merged, document.getPropertySource());};}這里面的addMethod又是一個回調(diào)方法,最終調(diào)用到
MutablePropertySources.addLast將這個資源文件對象加載到環(huán)境對象的propertList屬性中。設(shè)置兩個回調(diào)的地方在第一個load中,可以找到第一個看代碼。
load(profile, this::getPositiveProfileFilter,addToLoaded(MutablePropertySources::addLast, false)); public void addLast(PropertySource<?> propertySource) {synchronized (this.propertySourceList) {removeIfPresent(propertySource);this.propertySourceList.add(propertySource);}}?最終屬性數(shù)據(jù)存儲在內(nèi)部類loader
private Map<Profile, MutablePropertySources> loaded 屬性中。14.然后我們再回到第一個load函數(shù),他會不斷循環(huán)所有的profiles,因為之前已經(jīng)往這個profile列表中加上dev,所以會再次循環(huán)加載dev的profile配置,流程一樣。
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);}?這是加載dev的profile的配置屬性列表。
?然后回調(diào),就會把dev的文檔生成資源屬性文件添加到loaded列表。
?15.最后會調(diào)用addLoadedPropertySources把加載成功的配置屬性添加到環(huán)境對象的propertySources中。
private void addLoadedPropertySources() {MutablePropertySources destination = this.environment.getPropertySources();List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());Collections.reverse(loaded);String lastAdded = null;Set<String> added = new HashSet<>();for (MutablePropertySources sources : loaded) {for (PropertySource<?> source : sources) {if (added.add(source.getName())) {addLoadedPropertySource(destination, lastAdded, source);lastAdded = source.getName();}}}}加載前
?加載后
我們可以看到,是加載到尾部,這也證明了配置文件的優(yōu)先級最低。同時我們可以看到dev的配置文件是在默認(rèn)之前。所以會覆蓋默認(rèn)的。
注意上面添加代碼,有對loaded作倒序處理。
Collections.reverse(loaded);至此,配置文件屬性加載完畢。?
總結(jié)
以上是生活随笔為你收集整理的springboot 读取application.properties流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot @Configur
- 下一篇: springboot 读取bootStr