1. 程式人生 > >Springboot內建ApplicationListener--ConfigFileApplicationListener

Springboot內建ApplicationListener--ConfigFileApplicationListener

原始碼分析

本文原始碼基於 Springboot 2.1.0

package org.springframework.boot.context.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.
LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.
springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.boot.env.RandomValuePropertySource; 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.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.Profiles; import org.springframework.core.env.PropertySource; 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.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** * 這既是一個應用上下文事件監聽器,也是一個EnvironmentPostProcessor。它在相應的事件發生時從 * 約定的位置讀取配置檔案設定上下文環境屬性。 * EnvironmentPostProcessor that configures the context environment by loading * properties from well known file locations. * * 預設情況下,配置屬性會從以下路徑的'application.properties/yml'檔案中讀取: * By default properties will be loaded from 'application.properties' and/or * 'application.yml' files in the following locations: * * 1. classpath: * 2. file:./ * 3. classpath:config/ * 4. file:./config/: * * * Alternative search locations and names can be specified using * #setSearchLocations(String) and #setSearchNames(String). * 也可以設定其他的搜尋位置或者設定其它配置檔名稱: * #setSearchLocations(String) * #setSearchNames(String) * * 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. * 根據活躍profile配置的不同,可能還有其他屬性檔案會被載入。比如對於'web'應用, * 'application-web.properties/yml'會被載入。 * * 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. * 另外屬性'spring.config.location'/'spring.config.name'用於指定不同的搜尋位置和配置檔名稱。 * * * @author Dave Syer * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @author Eddú Meléndez * @author Madhura Bhave */ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { private static final String DEFAULT_PROPERTIES = "defaultProperties"; // Note the order is from least to most specific (last one wins) // 預設的配置檔案搜尋路徑(靠後的勝出) private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "application"; private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null); private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class); /** * The "active profiles" property name. */ public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; /** * The "includes profiles" property name. */ public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; /** * The "config name" property name. */ public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; /** * The "config location" property name. */ public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; /** * The "config additional location" property name. */ public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; /** * The default order for the processor. */ public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; private final DeferredLog logger = new DeferredLog(); private String searchLocations; private String names; private int order = DEFAULT_ORDER; @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) || ApplicationPreparedEvent.class.isAssignableFrom(eventType); } @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { // 應用上下文環境物件準備好時載入Spring內建的EnvironmentPostProcessor,再加上自己, // 排序,然後在當前應用上下文的環境物件上呼叫它們的postProcessEnvironment() List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } List<EnvironmentPostProcessor> loadPostProcessors() { // 載入Spring內建的EnvironmentPostProcessor return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); } @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); } private void onApplicationPreparedEvent(ApplicationEvent event) { this.logger.switchTo(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) */ protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { // 往指定環境物件中增加一個隨機數屬性源 RandomValuePropertySource.addToEnvironment(environment); // 載入環境物件中的PropertySources, new Loader(environment, resourceLoader).load(); } /** * Add appropriate post-processors to post-configure the property-sources. * 往應用上下文中增加一個PropertySourceOrderingPostProcessor,這是一個 * BeanFactoryPostProcessor,用於post configure PropertySources,其實是將名稱 * 為 defaultProperties 的 PropertySource 從 PropertySources 中拿掉,然後新增到 * PropertySources 的最後 * @param context the context to configure */ protected void addPostProcessors(ConfigurableApplicationContext context) { context.addBeanFactoryPostProcessor( new PropertySourceOrderingPostProcessor(context)); } public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.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 #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 */ public void setSearchLocations(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 */ public void setSearchNames(String names) { Assert.hasLength(names, "Names must not be empty"); this.names = names; } /** * BeanFactoryPostProcessor to re-order our property sources below any * @PropertySource items added by the ConfigurationClassPostProcessor. */ private class PropertySourceOrderingPostProcessor implements BeanFactoryPostProcessor, Ordered { private ConfigurableApplicationContext context; PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) { this.context = context; } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { reorderSources(this.context.getEnvironment()); } private void reorderSources(ConfigurableEnvironment environment) { PropertySource<?> defaultProperties = environment.getPropertySources() .remove(DEFAULT_PROPERTIES); if (defaultProperties != null) { environment.getPropertySources().addLast(defaultProperties); } } } /** * Loads candidate property sources and configures the active profiles. */ private class Loader { private final Log logger = ConfigFileApplicationListener.this.logger; private final ConfigurableEnvironment environment; private final PropertySourcesPlaceholdersResolver placeholdersResolver; private final ResourceLoader resourceLoader; private final List<PropertySourceLoader> propertySourceLoaders; private Deque<Profile> profiles; private List<Profile> processedProfiles; private boolean activatedProfiles; private Map<Profile, MutablePropertySources> loaded; private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>(); Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.placeholdersResolver = new PropertySourcesPlaceholdersResolver( this.environment); this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); this.propertySourceLoaders = SpringFactoriesLoader.loadFactories( PropertySourceLoader.class, getClass().getClassLoader()); } public void load() { 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 (profile != null && !profile.isDefaultProfile()) { addProfileToEnvironment(profile.getName()); } load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } resetEnvironmentProfiles(this.processedProfiles); load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); addLoadedPropertySources(); } /** * Initialize profile information from both the Environment active * profiles and any spring.profiles.active/spring.profiles.include * properties that are already set. */ 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. this.profiles.add(null); Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty(); this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty)); // Any pre-existing active profiles set via property sources (e.g. // System properties) take precedence over those added in config files. addActiveProfiles(activatedViaProperty); 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); } } } private Set<Profile> getProfilesActivatedViaProperty() { if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY) && !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) { return Collections.emptySet(); } Binder binder = Binder.get(this.environment); Set<Profile> activeProfiles = new LinkedHashSet<>(); activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY)); activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY)); return activeProfiles; } private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) { return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new) .filter((profile) -> !activatedViaProperty.contains(profile)) .collect(Collectors.toList()); } void addActiveProfiles(Set<Profile> profiles) { if (profiles.isEmpty()) { return; } if (this.activatedProfiles) { if (this.logger.isDebugEnabled()) { this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied"); } return; } this.profiles.addAll(profiles); if (this.logger.isDebugEnabled()) { this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles)); } this.activatedProfiles = true; removeUnprocessedDefaultProfiles(); } private void removeUnprocessedDefaultProfiles(