From: juejin. Cn/post / 684490…

This article mainly introduces the springBoot2.x configuration file parsing process, and also covers the change of SpringBoot2.x’s environment processing logic to SpringBoot1.x. SpringCloud’s configuration file parsing is an extension of this. Before springBoot parsing logic, added the bootstrap configuration, through the listener BootstrapApplicationListener implementation. More on this later.

An overview,

Environment is a highly abstract interface that Spring provides for the runtime, and all relevant configurations in a project run are based on this interface. SpringBoot extends this interface. Let’s start with a simple SpringBoot application.

@org.springframework.boot.autoconfigure.SpringBootApplication
@RestController
public class SpringBootApplication {

	@Autowired
	Environment environment;

	@RequestMapping(value = "/environment", method = RequestMethod.GET)
	public String environment() {system.out.println (environment.getClass());returnJSON.toJSONString(environment); }} Copy the codeCopy the code

What are the environment objects injected by the above code? Follow the source code, search for the answer.

Second, source code analysis

In the previous springBoot boot process, we mentioned that there is a prepareEnvironment method:

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Copy the codeCopy the code
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments ApplicationArguments) {// Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); / / 2, load the default configuration configureEnvironment (environment, applicationArguments getSourceArgs ()); / / 3 listeners, notifications, environment, load in the project profile listeners. EnvironmentPrepared (environment);bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		returnenvironment; } Duplicate codeCopy the code

Next, the above three steps will be analyzed in detail:

1. Initializationenvironment:

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if(this.environment ! = null) {returnthis.environment; } // Springboot application returns the environmentif (this.webApplicationType == WebApplicationType.SERVLET) {
			return new StandardServletEnvironment();
		}
		returnnew StandardEnvironment(); } Duplicate codeCopy the code

See matching according to the type of environment, and access to StandardServletEnvironment, the instance directly injected into the spring container, so it is the type of the output of the sample code above StandardServletEnvironment.

StandardServletEnvironment is the implementation of the application runtime environment springboot class, behind all the operations about the configuration and environment based on this. Take a look at the structure of this class:

First of all,
StandardServletEnvironmentInitialization of the parent class method must result in initialization of the parent class method:
AbstractEnvironment:

	public AbstractEnvironment() {// Load our custom configuration file customizePropertySources(this.propertysources);if (logger.isDebugEnabled()) {
			logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources "+ this.propertySources); }} Copy the codeCopy the code

Call the custom configuration file in the constructor. Spring’s usual, template mode, calls the custom logic of the instance object:

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if(JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources); } Duplicate codeCopy the code

Since this configuration class is web-based, the servlet-related parameters are loaded first, and addLast is placed last:

	/** System environment property source name: {@value} */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value} */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; Copy the codeCopy the code

This class is for StandardEnvironment extension, here will call super. CustomizePropertySources (propertySources); :

@Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); } Duplicate codeCopy the code
	/** System environment property source name: {@value} */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value} */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; Copy the codeCopy the code

You can see that the order is always at the back, so the first one is in front. SystemProperties is the systemEnvironment first, which is important. This is because the previous configuration overrides the later configuration, meaning that the configuration in the system variable takes precedence over the configuration in the system environment variable. As follows:

2) Load the default configuration:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {// Load the start command line configuration property configurePropertySources(Environment, args); // Set the active attribute configureProfiles(Environment, args); } Duplicate codeCopy the code

Here is the parameters of the receiving ConfigurableEnvironment, namely StandardServletEnvironment parent class. Follow up with the configurePropertySources method:

protected void configurePropertySources(ConfigurableEnvironment environment, String [] args) {/ / access to the configuration set MutablePropertySources sources = environment. GetPropertySources (); // Check whether there is a default configuration. The default is nullif(this.defaultProperties ! = null && ! this.defaultProperties.isEmpty()) { sources.addLast( new MapPropertySource("defaultProperties", this.defaultProperties)); } // Load the command line configurationif (this.addCommandLineProperties && args.length > 0) {
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
			if(sources.contains(name)) { PropertySource<? >source = sources.get(name);
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(new SimpleCommandLinePropertySource(
						"springApplicationCommandLineArgs", args));
				composite.addPropertySource(source);
				sources.replace(name, composite);
			}
			else{ sources.addFirst(new SimpleCommandLinePropertySource(args)); }}} copy the codeCopy the code

The above code does two main things: it determines whether SpringBootApplication specifies a default configuration, and it loads the default command line configuration.

There’s a core key class up there,MutablePropertySourcesMutable is a class that encapsulates a collection of attribute resources:

public class MutablePropertySources implements PropertySources { private final Log logger; private final List<PropertySource<? >> propertySourceList = new CopyOnWriteArrayList<>(); } Duplicate codeCopy the code

How is this class used?

The design here is very clever, willMutablePropertySourcesPass to the file parserpropertyResolver, at the same timeAbstractEnvironmentIt also implements file parsing interfaceConfigurablePropertyResolver, soAbstractEnvironmentYou have file parsing. soStandardServletEnvironmentFile parsing is actually delegated toPropertySourcesPropertyResolverTo implement the.

Take a look at configureProfiles(Environment, ARGS); Methods:

	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		environment.getActiveProfiles(); // ensure they are initialized
		// But these ones should go first (last wins ina property key clash) Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); } Duplicate codeCopy the code

This method mainly loads the additionalProfiles file specified in SpringBootApplication into the environment, which is generally empty by default. The use of this variable, in the project launch class, needs to show the creation of a SpringApplication instance as follows:

SpringApplication springApplication = new SpringApplication(MyApplication.class); / / set the profile variable springApplication. SetAdditionalProfiles ("prd"); springApplication.run(MyApplication.class,args); Copy the codeCopy the code

3. Notify the environment listener to load the configuration file in the project

Trigger listener:

listeners.environmentPrepared(environment); Copy the codeCopy the code

In SpringBoot2 | SpringBoot startup process source code analysis the method mentioned in (a) notify the listeners, and configuration files related to the listener type for ConfigFileApplicationListener, listening to the event performed:

@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {/ / loaded in the project profile addPropertySources (environment, application getResourceLoader ()); configureIgnoreBeanInfo(environment);bindToSpringApplication(environment, application); } Duplicate codeCopy the code

If you follow along, you’ll find a core inner class Loader that delegates configuration file loading:

private class Loader { private final Log logger = ConfigFileApplicationListener.this.logger; // Private final ConfigurableEnvironment; // Class loader, which can be specified in the SpringApplication constructor at project startup. By default, the Launcher.AppClassLoader is used. Private final List<PropertySourceLoader> propertySourceLoaders; //LIFO Queue private Queue<String> profiles; // Processed files private List<String> processedProfiles; private boolean activatedProfiles; Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; This. resourceLoader = resourceLoader == null? new DefaultResourceLoader() : resourceLoader; / / get propertySourceLoaders enclosing propertySourceLoaders = SpringFactoriesLoader. LoadFactories (PropertySourceLoader. Class, getClass().getClassLoader()); } / /... } Duplicate codeCopy the code

The above propertySourceLoaders get all the implementation classes of type PropertySourceLoader in the current project through the SpringFactoriesLoader. There are two implementation classes by default, as shown below:

Moving on to the main parsing method: load() :

		public void load() {
			this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false; this.loaded = new LinkedHashMap<>(); // Initialize the logic initializeProfiles(); // Locate the parse resource filewhile(! this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast,false)); this.processedProfiles.add(profile); } / / prioritize the load loaded configuration file (null, this: : getNegativeProfileFilter, addToLoaded (MutablePropertySources: : addFirst,true)); addLoadedPropertySources(); } Duplicate codeCopy the code

Follow the initialization method above:

	private void initializeProfiles() { Set<Profile> initialActiveProfiles = initializeActiveProfiles(); this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles)); // If empty, add the default profileif (this.profiles.isEmpty()) {
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					if(! this.profiles.contains(defaultProfile)) { this.profiles.add(defaultProfile); } } } // The default profilefor 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 inThe defaults when the list is reversed later). // Add a null profile, essentially loading the default profile this.profiles. Add (null); } Duplicate codeCopy the code

There are two main things it does:

1) Check whether a profile is specified. If not, add the default environment: default. Default files, such as application-default.yml and application-default.properties, are parsed in the following flow.

Note: In step 2 we mentioned the additionalProfiles property. If we specify a profile through this property, the default profile will not be loaded and will be matched against the profile we specify.

2) Add a null profile, mainly used to load profiles that do not specify a profile, such as application.properties because profiles uses LIFO queues, last in, first out. A null profile will be loaded first, i.e. Application. Properties, application.

Load:

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {get the default configuration file path getSearchLocations().foreach ((location) -> {Boolean isFolder = location.endsWith("/"); Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES); // loop loading names.foreach ((name) -> Load (location, name, profile, filterFactory, consumer)); }); } Duplicate codeCopy the code

You can see that the new changes underlying springBoot2.0 are all implemented based on lambda expressions.

Follow up with the getSearchLocations() method:

After obtaining the path, it will concatenate the configuration file name and select the appropriate YML or Properties parser for parsing: (name) -> Load (location, name, profile, filterFactory, consumer)

1) Obtain the default configuration file paths. There are four paths. 2) Traverse all paths and assemble configuration file names. 3) Iterate through the parser again, select EITHER YML or Properties parsing, and add the parsing result to the collection MutablePropertySources.

The final parsing result is as follows:

At this point, the resource files in springBoot are loaded and parsed from top to bottom, so the previous configuration file overrides the later one. You can see that application.properties has the lowest priority, and system variables and environment variables have relatively high priority.

Springboot2.x is different from SpringBoot1.x

There are two major changes:

In springBoot1. x, if we define application-default.properties file, the priority order is:

Properties > application-dev.properties > application.properties Copies the codeCopy the code

In springBoot2.x, if we define the application-default.properties file, there are two cases:

1) The application-dev.properties file is not configured.

Application-default. properties > application.properties Copies the codeCopy the code

Application dev.properties file is configured with priority order:

Application-dev. Properties > application.properties Copies the codeCopy the code

This is because in version 2.x, if application-dev.properties is defined, the application-default.properties file will be deleted.

If there is an active file, the default profile will be deleted:

private void maybeActivateProfiles(Set<Profile> profiles) {
            if (profiles.isEmpty()) {
                return;
            }
            if (this.activatedProfiles) {
                this.logger.debug("Profiles already activated, '" + profiles
                        + "' will not be applied");
                return;
            }
            addProfiles(profiles);
            this.logger.debug("Activated profiles "
                    + StringUtils.collectionToCommaDelimitedString(profiles));
            this.activatedProfiles = true; / / delete the default configuration file, application - default. * file removeUnprocessedDefaultProfiles (); } private voidremoveUnprocessedDefaultProfiles() { this.profiles.removeIf(Profile::isDefaultProfile); } Duplicate codeCopy the code

Second, SpringBoot 1 x version, unified project configuration file is encapsulated inside classes ConfigFileApplicationListener $ConfigurationPropertySources object, deposited in the environment. In 2 x version, the configuration file is separately packaged into OriginTrackedMapPropertySource into the environment

Note these two points when upgrading SpringBoot1.x to SpringBoot2.x.