/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/package org.springframework.boot.context.config;import java.io.IOException;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.LinkedList;import java.util.List;import java.util.Queue;import java.util.Set;import org.apache.commons.logging.Log;import org.springframework.beans.BeansException;import org.springframework.beans.CachedIntrospectionResults;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.boot.SpringApplication;import org.springframework.boot.bind.PropertiesConfigurationFactory;import org.springframework.boot.bind.PropertySourcesPropertyValues;import org.springframework.boot.bind.RelaxedDataBinder;import org.springframework.boot.bind.RelaxedPropertyResolver;import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;import org.springframework.boot.context.event.ApplicationPreparedEvent;import org.springframework.boot.env.EnumerableCompositePropertySource;import org.springframework.boot.env.EnvironmentPostProcessor;import org.springframework.boot.env.PropertySourcesLoader;import org.springframework.boot.logging.DeferredLog;import org.springframework.context.ApplicationEvent;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ConfigurationClassPostProcessor;import org.springframework.context.event.SmartApplicationListener;import org.springframework.core.Ordered;import org.springframework.core.annotation.AnnotationAwareOrderComparator;import org.springframework.core.convert.ConversionService;import org.springframework.core.convert.support.DefaultConversionService;import org.springframework.core.env.ConfigurableEnvironment;import org.springframework.core.env.EnumerablePropertySource;import org.springframework.core.env.MutablePropertySources;import org.springframework.core.env.PropertySource;import org.springframework.core.env.PropertySources;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.Resource;import org.springframework.core.io.ResourceLoader;import org.springframework.core.io.support.SpringFactoriesLoader;import org.springframework.util.Assert;import org.springframework.util.ResourceUtils;import org.springframework.util.StringUtils;import org.springframework.validation.BindException;/**
* {@link EnvironmentPostProcessor} that configures the context environment by loading
* properties from well known file locations. By default properties will be loaded from
* 'application.properties' and/or 'application.yml' files in the following locations:
* <ul>
* <li>classpath:</li>
* <li>file:./</li>
* <li>classpath:config/</li>
* <li>file:./config/:</li>
* </ul>
* <p>
* Alternative search locations and names can be specified using
* {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}.
* <p>
* Additional files will also be loaded based on active profiles. For example if a 'web'
* profile is active 'application-web.properties' and 'application-web.yml' will be
* considered.
* <p>
* The 'spring.config.name' property can be used to specify an alternative name to load
* and the 'spring.config.location' property can be used to specify alternative search
* locations or specific files.
* <p>
* Configuration properties are also bound to the {@link SpringApplication}. This makes it
* possible to set {@link SpringApplication} properties dynamically, like the sources
* ("spring.main.sources" - a CSV list) the flag to indicate a web environment
* ("spring.main.web_environment=true") or the flag to switch off the banner
* ("spring.main.show_banner=false").
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Eddú Meléndez
*/publicclassConfigFileApplicationListenerimplementsEnvironmentPostProcessor, SmartApplicationListener, Ordered {privatestaticfinal String DEFAULT_PROPERTIES ="defaultProperties";// Note the order is from least to most specific (last one wins)privatestaticfinal String DEFAULT_SEARCH_LOCATIONS
="classpath:/,classpath:/config/,file:./,file:./config/";privatestaticfinal String DEFAULT_NAMES ="application";/**
* The "active profiles" property name.
*/publicstaticfinal String ACTIVE_PROFILES_PROPERTY ="spring.profiles.active";/**
* The "includes profiles" property name.
*/publicstaticfinal String INCLUDE_PROFILES_PROPERTY ="spring.profiles.include";/**
* The "config name" property name.
*/publicstaticfinal String CONFIG_NAME_PROPERTY ="spring.config.name";/**
* The "config location" property name.
*/publicstaticfinal String CONFIG_LOCATION_PROPERTY ="spring.config.location";/**
* The default order for the processor.
*/publicstaticfinalint DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE +10;/**
* Name of the application configuration {@link PropertySource}.
*/publicstaticfinal String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME
="applicationConfigurationProperties";privatefinal DeferredLog logger =newDeferredLog();private String searchLocations;private String names;privateint order = DEFAULT_ORDER;privatefinal ConversionService conversionService =newDefaultConversionService();@OverridepublicbooleansupportsEventType(Class<?extendsApplicationEvent> eventType){return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)|| ApplicationPreparedEvent.class.isAssignableFrom(eventType);}@OverridepublicbooleansupportsSourceType(Class<?> aClass){returntrue;}
@OverridepublicvoidpostProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application){// 行号195: 读配置文件放入environment addPropertySources(environment, application.getResourceLoader());configureIgnoreBeanInfo(environment);// 行号197: 从environment中获取之前放入的配置文件内容bindToSpringApplication(environment, application);}privatevoidconfigureIgnoreBeanInfo(ConfigurableEnvironment environment){if(System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME)== null){
RelaxedPropertyResolver resolver =newRelaxedPropertyResolver(environment,"spring.beaninfo.");
Boolean ignore = resolver.getProperty("ignore", Boolean.class, Boolean.TRUE);
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());}}privatevoidonApplicationPreparedEvent(ApplicationEvent event){this.logger.replayTo(ConfigFileApplicationListener.class);addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());}/**
* Add config file property sources to the specified environment.
* @param environment the environment to add source to
* @param resourceLoader the resource loader
* @see #addPostProcessors(ConfigurableApplicationContext)
*/protectedvoidaddPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader){
RandomValuePropertySource.addToEnvironment(environment);newLoader(environment, resourceLoader).load();}/**
* Bind the environment to the {@link SpringApplication}.
* @param environment the environment to bind
* @param application the application to bind to
*/protectedvoidbindToSpringApplication(ConfigurableEnvironment environment,
SpringApplication application){
PropertiesConfigurationFactory<SpringApplication> binder
=newPropertiesConfigurationFactory<SpringApplication>(
application);
binder.setTargetName("spring.main");
binder.setConversionService(this.conversionService);
binder.setPropertySources(environment.getPropertySources());try{
binder.bindPropertiesToTarget();}catch(BindException ex){thrownewIllegalStateException("Cannot bind to SpringApplication", ex);}}/**
* Add appropriate post-processors to post-configure the property-sources.
* @param context the context to configure
*/protectedvoidaddPostProcessors(ConfigurableApplicationContext context){
context.addBeanFactoryPostProcessor(newPropertySourceOrderingPostProcessor(context));}publicvoidsetOrder(int order){this.order = order;}@OverridepublicintgetOrder(){returnthis.order;}/**
* Set the search locations that will be considered as a comma-separated list. Each
* search location should be a directory path (ending in "/") and it will be prefixed
* by the file names constructed from {@link #setSearchNames(String) search names} and
* profiles (if any) plus file extensions supported by the properties loaders.
* Locations are considered in the order specified, with later items taking precedence
* (like a map merge).
* @param locations the search locations
*/publicvoidsetSearchLocations(String locations){
Assert.hasLength(locations,"Locations must not be empty");this.searchLocations = locations;}/**
* Sets the names of the files that should be loaded (excluding file extension) as a
* comma-separated list.
* @param names the names to load
*/publicvoidsetSearchNames(String names){
Assert.hasLength(names,"Names must not be empty");this.names = names;}/**
* {@link BeanFactoryPostProcessor} to re-order our property sources below any
* {@code @PropertySource} items added by the {@link ConfigurationClassPostProcessor}.
*/privateclassPropertySourceOrderingPostProcessorimplementsBeanFactoryPostProcessor, Ordered {private ConfigurableApplicationContext context;PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context){this.context = context;}@OverridepublicintgetOrder(){return Ordered.HIGHEST_PRECEDENCE;}@OverridepublicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throws BeansException {reorderSources(this.context.getEnvironment());}privatevoidreorderSources(ConfigurableEnvironment environment){
ConfigurationPropertySources
.finishAndRelocate(environment.getPropertySources());
PropertySource<?> defaultProperties = environment.getPropertySources().remove(DEFAULT_PROPERTIES);if(defaultProperties != null){
environment.getPropertySources().addLast(defaultProperties);}}}/**
* Loads candidate property sources and configures the active profiles.
*/privateclassLoader{privatefinal Log logger = ConfigFileApplicationListener.this.logger;privatefinal ConfigurableEnvironment environment;privatefinal ResourceLoader resourceLoader;private PropertySourcesLoader propertiesLoader;private Queue<Profile> profiles;private List<Profile> processedProfiles;privateboolean activatedProfiles;Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader){this.environment = environment;this.resourceLoader = resourceLoader == null ?newDefaultResourceLoader(): resourceLoader;}
load
publicvoidload(){this.propertiesLoader =newPropertySourcesLoader();this.activatedProfiles =false;this.profiles = Collections.asLifoQueue(newLinkedList<Profile>());this.processedProfiles =newLinkedList<Profile>();// Pre-existing active profiles set via Environment.setActiveProfiles()// are additional profiles and config files are allowed to add more if// they want to, so don't call addActiveProfiles() here.
Set<Profile> initialActiveProfiles =initializeActiveProfiles();this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));if(this.profiles.isEmpty()){for(String defaultProfileName :this.environment.getDefaultProfiles()){
Profile defaultProfile =newProfile(defaultProfileName,true);if(!this.profiles.contains(defaultProfile)){this.profiles.add(defaultProfile);}}}// The default profile for these purposes is represented as null. We add it// last so that it is first out of the queue (active profiles will then// override any settings in the defaults when the list is reversed later).this.profiles.add(null);while(!this.profiles.isEmpty()){
Profile profile =this.profiles.poll();for(String location :getSearchLocations()){if(!location.endsWith("/")){// location is a filename already, so don't search for more// filenamesload(location, null, profile);}else{//getSearchNames(): 从environment中获取属性spring.config.name的值// 如果没有设置则取默认的值:applicationfor(String name :getSearchNames()){load(location, name, profile);}}}this.processedProfiles.add(profile);}addConfigurationProperties(this.propertiesLoader.getPropertySources());}private Set<Profile>initializeActiveProfiles(){if(!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)&&!this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)){return Collections.emptySet();}// Any pre-existing active profiles set via property sources (e.g. System// properties) take precedence over those added in config files.
SpringProfiles springProfiles =bindSpringProfiles(this.environment.getPropertySources());
Set<Profile> activeProfiles =newLinkedHashSet<Profile>(
springProfiles.getActiveProfiles());
activeProfiles.addAll(springProfiles.getIncludeProfiles());maybeActivateProfiles(activeProfiles);return activeProfiles;}/**
* Return the active profiles that have not been processed yet. If a profile is
* enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and
* {@link ConfigurableEnvironment#addActiveProfile(String)} it needs to be
* filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes precedence.
* <p>
* Concretely, if the "cloud" profile is enabled via the environment, it will take
* less precedence that any profile set via the {@link #ACTIVE_PROFILES_PROPERTY}.
* @param initialActiveProfiles the profiles that have been enabled via
* {@link #ACTIVE_PROFILES_PROPERTY}
* @return the unprocessed active profiles from the environment to enable
*/private List<Profile>getUnprocessedActiveProfiles(
Set<Profile> initialActiveProfiles){
List<Profile> unprocessedActiveProfiles =newArrayList<Profile>();for(String profileName :this.environment.getActiveProfiles()){
Profile profile =newProfile(profileName);if(!initialActiveProfiles.contains(profile)){
unprocessedActiveProfiles.add(profile);}}// Reverse them so the order is the same as from getProfilesForValue()// (last one wins when properties are eventually resolved)
Collections.reverse(unprocessedActiveProfiles);return unprocessedActiveProfiles;}privatevoidload(String location, String name, Profile profile){
String group ="profile="+(profile == null ?"": profile);if(!StringUtils.hasText(name)){// Try to load directly from the locationloadIntoGroup(group, location, profile);}else{// Search for a file with the given namefor(String ext :this.propertiesLoader.getAllFileExtensions()){if(profile != null){// Try the profile-specific fileloadIntoGroup(group, location + name +"-"+ profile +"."+ ext,
null);for(Profile processedProfile :this.processedProfiles){if(processedProfile != null){loadIntoGroup(group, location + name +"-"+ processedProfile +"."+ ext, profile);}}// Sometimes people put "spring.profiles: dev" in// application-dev.yml (gh-340). Arguably we should try and error// out on that, but we can be kind and load it anyway.loadIntoGroup(group, location + name +"-"+ profile +"."+ ext,
profile);}// Also try the profile-specific section (if any) of the normal file// 加载默认的文件 application.(ext),而不是profile指定的文件loadIntoGroup(group, location + name +"."+ ext, profile);}}}private PropertySource<?>loadIntoGroup(String identifier, String location,
Profile profile){try{returndoLoadIntoGroup(identifier, location, profile);}catch(Exception ex){thrownewIllegalStateException("Failed to load property source from location '"+ location +"'",
ex);}}private PropertySource<?>doLoadIntoGroup(String identifier, String location,
Profile profile)throws IOException {// 将location指定位置的文件包装为Resource 类型的对象
Resource resource =this.resourceLoader.getResource(location);
PropertySource<?> propertySource = null;
StringBuilder msg =newStringBuilder();if(resource != null && resource.exists()){
String name ="applicationConfig: ["+ location +"]";
String group ="applicationConfig: ["+ identifier +"]";//加载配置文件,返回PropertySource类型的配置文件内容
propertySource =this.propertiesLoader.load(resource, group, name,(profile == null ? null : profile.getName()));if(propertySource != null){
msg.append("Loaded ");//handleProfileProperties(propertySource);}else{
msg.append("Skipped (empty) ");}}else{
msg.append("Skipped ");}
msg.append("config file ");
msg.append(getResourceDescription(location, resource));if(profile != null){
msg.append(" for profile ").append(profile);}if(resource == null ||!resource.exists()){
msg.append(" resource not found");this.logger.trace(msg);}else{this.logger.debug(msg);}return propertySource;}private String getResourceDescription(String location, Resource resource){
String resourceDescription ="'"+ location +"'";if(resource != null){try{
resourceDescription = String.format("'%s' (%s)",
resource.getURI().toASCIIString(), location);}catch(IOException ex){// Use the location as the description}}return resourceDescription;}privatevoidhandleProfileProperties(PropertySource<?> propertySource){
SpringProfiles springProfiles =bindSpringProfiles(propertySource);maybeActivateProfiles(springProfiles.getActiveProfiles());addProfiles(springProfiles.getIncludeProfiles());}private SpringProfiles bindSpringProfiles(PropertySource<?> propertySource){
MutablePropertySources propertySources =newMutablePropertySources();
propertySources.addFirst(propertySource);returnbindSpringProfiles(propertySources);}private SpringProfiles bindSpringProfiles(PropertySources propertySources){
SpringProfiles springProfiles =newSpringProfiles();
RelaxedDataBinder dataBinder =newRelaxedDataBinder(springProfiles,"spring.profiles");
dataBinder.bind(newPropertySourcesPropertyValues(propertySources,false));
springProfiles.setActive(resolvePlaceholders(springProfiles.getActive()));
springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));return springProfiles;}private List<String>resolvePlaceholders(List<String> values){
List<String> resolved =newArrayList<String>();for(String value : values){
resolved.add(this.environment.resolvePlaceholders(value));}return resolved;}privatevoidmaybeActivateProfiles(Set<Profile> profiles){if(this.activatedProfiles){if(!profiles.isEmpty()){this.logger.debug("Profiles already activated, '"+ profiles
+"' will not be applied");}return;}if(!profiles.isEmpty()){addProfiles(profiles);this.logger.debug("Activated profiles "+ StringUtils.collectionToCommaDelimitedString(profiles));this.activatedProfiles =true;removeUnprocessedDefaultProfiles();}}privatevoidremoveUnprocessedDefaultProfiles(){for(Iterator<Profile> iterator =this.profiles.iterator(); iterator
.hasNext();){if(iterator.next().isDefaultProfile()){
iterator.remove();}}}privatevoidaddProfiles(Set<Profile> profiles){for(Profile profile : profiles){this.profiles.add(profile);if(!environmentHasActiveProfile(profile.getName())){// If it's already accepted we assume the order was set// intentionallyprependProfile(this.environment, profile);}}}privatebooleanenvironmentHasActiveProfile(String profile){for(String activeProfile :this.environment.getActiveProfiles()){if(activeProfile.equals(profile)){returntrue;}}returnfalse;}privatevoidprependProfile(ConfigurableEnvironment environment,
Profile profile){
Set<String> profiles =newLinkedHashSet<String>();
environment.getActiveProfiles();// ensure they are initialized// But this one should go first (last wins in a property key clash)
profiles.add(profile.getName());
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(profiles.toArray(newString[profiles.size()]));}private Set<String>getSearchLocations(){
Set<String> locations =newLinkedHashSet<String>();// User-configured settings take precedence, so we do them firstif(this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)){for(String path :asResolvedSet(this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)){if(!path.contains("$")){
path = StringUtils.cleanPath(path);if(!ResourceUtils.isUrl(path)){
path = ResourceUtils.FILE_URL_PREFIX + path;}}
locations.add(path);}}
locations.addAll(asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));return locations;}private Set<String>getSearchNames(){if(this.environment.containsProperty(CONFIG_NAME_PROPERTY)){returnasResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);}returnasResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);}private Set<String>asResolvedSet(String value, String fallback){
List<String> list = Arrays.asList(StringUtils.trimArrayElements(
StringUtils.commaDelimitedListToStringArray(value != null
?this.environment.resolvePlaceholders(value): fallback)));
Collections.reverse(list);returnnewLinkedHashSet<String>(list);}privatevoidaddConfigurationProperties(MutablePropertySources sources){
List<PropertySource<?>> reorderedSources =newArrayList<PropertySource<?>>();for(PropertySource<?> item : sources){
reorderedSources.add(item);}addConfigurationProperties(newConfigurationPropertySources(reorderedSources));}
addConfigurationProperties
privatevoidaddConfigurationProperties(
ConfigurationPropertySources configurationSources){// 获取环境变量(environment)的实例变量propertySources// 将(1~n个)配置文件放入其中
MutablePropertySources existingSources =this.environment
.getPropertySources();if(existingSources.contains(DEFAULT_PROPERTIES)){
existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);}else{
existingSources.addLast(configurationSources);}}}privatestaticclassProfile{privatefinal String name;privatefinalboolean defaultProfile;Profile(String name){this(name,false);}Profile(String name,boolean defaultProfile){
Assert.notNull(name,"Name must not be null");this.name = name;this.defaultProfile = defaultProfile;}public String getName(){returnthis.name;}publicbooleanisDefaultProfile(){returnthis.defaultProfile;}@Overridepublic String toString(){returnthis.name;}@OverridepublicinthashCode(){returnthis.name.hashCode();}@Overridepublicbooleanequals(Object obj){if(obj ==this){returntrue;}if(obj == null || obj.getClass()!=getClass()){returnfalse;}return((Profile) obj).name.equals(this.name);}}/**
* Holds the configuration {@link PropertySource}s as they are loaded can relocate
* them once configuration classes have been processed.
*/staticclassConfigurationPropertySourcesextendsEnumerablePropertySource<Collection<PropertySource<?>>>{privatefinal Collection<PropertySource<?>> sources;privatefinal String[] names;ConfigurationPropertySources(Collection<PropertySource<?>> sources){super(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME, sources);this.sources = sources;
List<String> names =newArrayList<String>();for(PropertySource<?> source : sources){if(source instanceofEnumerablePropertySource){
names.addAll(Arrays.asList(((EnumerablePropertySource<?>) source).getPropertyNames()));}}this.names = names.toArray(newString[names.size()]);}@Overridepublic Object getProperty(String name){for(PropertySource<?> propertySource :this.sources){
Object value = propertySource.getProperty(name);if(value != null){return value;}}return null;}publicstaticvoidfinishAndRelocate(MutablePropertySources propertySources){
String name = APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME;
ConfigurationPropertySources removed =(ConfigurationPropertySources) propertySources
.get(name);if(removed != null){for(PropertySource<?> propertySource : removed.sources){if(propertySource instanceofEnumerableCompositePropertySource){
EnumerableCompositePropertySource composite
=(EnumerableCompositePropertySource) propertySource;for(PropertySource<?> nested : composite.getSource()){
propertySources.addAfter(name, nested);
name = nested.getName();}}else{
propertySources.addAfter(name, propertySource);}}
propertySources.remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);}}@Overridepublic String[]getPropertyNames(){returnthis.names;}}/**
* Holder for {@code spring.profiles} properties.
*/staticfinalclassSpringProfiles{private List<String> active =newArrayList<String>();private List<String> include =newArrayList<String>();public List<String>getActive(){returnthis.active;}publicvoidsetActive(List<String> active){this.active = active;}public List<String>getInclude(){returnthis.include;}publicvoidsetInclude(List<String> include){this.include = include;}
Set<Profile>getActiveProfiles(){returnasProfileSet(this.active);}
Set<Profile>getIncludeProfiles(){returnasProfileSet(this.include);}private Set<Profile>asProfileSet(List<String> profileNames){
List<Profile> profiles =newArrayList<Profile>();for(String profileName : profileNames){
profiles.add(newProfile(profileName));}
Collections.reverse(profiles);returnnewLinkedHashSet<Profile>(profiles);}}}
/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http:/