Spring boot configuration file parsing process

1, Auto configuration class PropertyPlaceholderAutoConfiguration

Spring boot configuration file parsing is realized through its automatic assembly principle. For the principle of automatic assembly, please refer to Core principles of SpringBoot.

The automatic configuration class resolved by the configuration file in Springboot is PropertyPlaceholderAutoConfiguration.

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
	public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
}

2, PropertySourcesPlaceholderConfigurer

SpringBoot loads a PropertySourcesPlaceholderConfigurer class through the automatic assembly principle. This class implements the beanfactory postprocessor interface in Spring. When the container is started, Spring will call the postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) method to complete some initialization actions.

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (this.propertySources == null) {
            this.propertySources = new MutablePropertySources();
            if (this.environment != null) {
            	// Encapsulate the Environment object into a PropertySource object and add it to the list of MutablePropertySources
                this.propertySources.addLast(new PropertySource<Environment>("environmentProperties", this.environment) {
                    @Nullable
                    public String getProperty(String key) {// Called when getting the value in the Environment
                        return ((Environment)this.source).getProperty(key);
                    }
                });
            }

            try {
            	// Load the local configuration file and wrap it as a PropertySource object
                PropertySource<?> localPropertySource = new PropertiesPropertySource("localProperties", this.mergeProperties());
                // Add to the list of MutablePropertySources
                if (this.localOverride) {// Overwrite local configuration file: if the key s are the same, the end of the queue overwrites the head of the queue
                    this.propertySources.addFirst(localPropertySource);// Team leader
                } else {
                    this.propertySources.addLast(localPropertySource);// Team tail
                }
            } catch (IOException var3) {
                throw new BeanInitializationException("Could not load properties", var3);
            }
        }

		// Resolve attribute: resolve and assign the attribute of @ Value annotation in Spring IoC container
        this.processProperties(beanFactory, (ConfigurablePropertyResolver)(new PropertySourcesPropertyResolver(this.propertySources)));
        this.appliedPropertySources = this.propertySources;
    }
}

2.1 PropertySourcesPlaceholderConfigurer.processProperties()

The processProperties() method in PropertySourcesPlaceholderConfigurer mainly parses properties and assigns values to properties marked with @ value ("spring. Application. Name: finpc") annotations. The main codes are as follows.

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
	/**
	 * Attribute resolution
	 */
	protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, final ConfigurablePropertyResolver propertyResolver) throws BeansException {
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);// Set prefix${
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);// Set suffix}
        propertyResolver.setValueSeparator(this.valueSeparator);// Set separator

		// Create a value parser of String type: during assignment, resolveStringValue(String strVal) will be called; method
        StringValueResolver valueResolver = (strVal) -> {
            String resolved = this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal);
            if (this.trimValues) {
                resolved = resolved.trim();
            }

            return resolved.equals(this.nullValue) ? null : resolved;
        };
		// Resolve the attribute value of @ value ("spring. Application. Name: finpc") annotation
        this.doProcessProperties(beanFactoryToProcess, valueResolver);
    }
	
	/**
	 * Resolve the attribute value of @ value ("spring. Application. Name: finpc") annotation
	 */
	protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
			StringValueResolver valueResolver) {
		// Packaging valueResolver
		BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
		
		// Get all beannames in the container
		String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
		for (String curName : beanNames) {// Loop traversal
			// Check that we're not parsing our own bean definition,
			// to avoid failing on unresolvable placeholders in properties file locations.
			if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {// Filter discharge
				BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
				try {
					// Resolve the value of the attribute in beanDefinition. For example, ${xx.xx} resolves to the real configuration attribute value
					// The core logic of this method will call the resolveStringValue(String strVal) method of the StringValueResolver object created above
					visitor.visitBeanDefinition(bd);
				}
				catch (Exception ex) {
					throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
				}
			}
		}

		// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
		beanFactoryToProcess.resolveAliases(valueResolver);

		// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
		beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
	}
}

2.2 StringValueResolver.resolveStringValue(String strVal)

The StringValueResolver object is a Value parser of type String and is the core object for parsing @ Value annotation.

StringValueResolver valueResolver = (strVal) -> {
		// Ignoreunresolved placeholders: whether to ignore non resolvable placeholders. The default is false
        String resolved = this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal);
        if (this.trimValues) {
            resolved = resolved.trim();
        }

        return resolved.equals(this.nullValue) ? null : resolved;
    };

2.3 propertyResolver.resolveRequiredPlaceholders(strVal)

Resolve strVal through resolveRequiredPlaceholders() method of AbstractPropertyResolver class.

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
	@Override
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		if (this.strictHelper == null) {
			this.strictHelper = createPlaceholderHelper(false);
		}
		return doResolvePlaceholders(text, this.strictHelper);
	}
	
	private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
		return helper.replacePlaceholders(text, this::getPropertyAsRawString);
	}
}

Finally, the replaceplaceholders (string value, placeholderresolver, placeholderresolver) method of PropertyPlaceholderHelper will be called to complete the resolution.

public class PropertyPlaceholderHelper {
	public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
		Assert.notNull(value, "'value' must not be null");
		return parseStringValue(value, placeholderResolver, null);
	}
	
	protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
		// Start with prefix${
		int startIndex = value.indexOf(this.placeholderPrefix);
		if (startIndex == -1) {
			return value;
		}

		StringBuilder result = new StringBuilder(value);
		while (startIndex != -1) {
			// Gets the position of the last suffix}
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				// Intercept the string between ${and}
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				if (visitedPlaceholders == null) {
					visitedPlaceholders = new HashSet<>(4);
				}
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				// Recursive call to resolve parameters of ${${}} type
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				
				// Now obtain the value for the fully resolved key...
				// Get the resolved value and call the getPropertyAsRawString(String key) method in ConfigurationPropertySourcesPropertyResolver
				// Call the getProperty(String key) method in propertySources, that is, call the getProperty(key) method of the environment object
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				
				if (propVal == null && this.valueSeparator != null) {// If it is blank, the default value is taken
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					if (separatorIndex != -1) {
						String actualPlaceholder = placeholder.substring(0, separatorIndex);
						// Gets the default value after the ':' sign
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						// Parameter resolution before ':'
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						// If it cannot be resolved, the default value is used
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					// Recursive call to resolve parameters of type ${${}}
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in value \"" + value + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}
		return result.toString();
	}
}

You can see that the PropertySourcesPlaceholderConfigurer mainly does three things during startup:

  1. Encapsulate the Environment object into a PropertySource object and add it to the list of MutablePropertySources.
  2. Load the local configuration file, wrap it into a PropertySource object, and add it to the list of MutablePropertySources.
  3. Resolve attribute: resolve and assign the attribute of @ Value annotation in Spring IoC container.

There are two very important objects, PropertySource and Environment. When SpringBoot initializes the properties in the IoC container, it will call the getProperty(String key) method of PropertySource, and finally directly call the getProperty(key) method of Environment.

3, PropertySource

PropertySource can be understood as a source of configuration properties, such as an application The properties configuration content is stored in a PropertySource object. All configuration property sources will be encapsulated as a PropertySource object. Spring starts initialization and obtains the property Value of the key in @ Value through the getProperty() method of the object.

PropertySource is an abstract class. The main properties and abstract methods are as follows.

public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());

	protected final String name;

	protected final T source;

	@Nullable
	public abstract Object getProperty(String name);
}
  • Name is the name identifying the property source. A PropertySource object can be found according to name.
  • Source is a structure used to store attribute key s and value s. For example, source can be a map or properties.
  • getProperty(String name) method: get the property value corresponding to the key according to the key.

In the PropertySourcesPlaceholderConfigurer above, an environment object is encapsulated as a PropertySource object. When obtaining the value, the getProperty() method of the environment object will be called.

4, Environment

4.1 initialization of environment object

In the SpringBoot startup main method, the run() method in SpringApplication will be executed, where an Environment object will be created and initialized. The specific code is as follows.

public class SpringApplication {
	public ConfigurableApplicationContext run(String... args) {
		// Create a StopWatch object and record the start-up time of run()
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // Create bootstrap context
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        // From meta-inf / spring. Under the classpath Get the full path array of all corresponding spring applicationrunlistener from the factories file
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        // Start SpringApplicationRunListener
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
        	// Load application Properties and external property configuration
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            /*************Prepare the spring environment according to the listener and default parameters***************/
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            // To print the Banner, you can customize the startup logo (create a banner.txt file in the resources path and put the icons you want to print into it)
            Banner printedBanner = this.printBanner(environment);
            // Create an ApplicationContext container, and determine the container type according to the WebApplicationType type
            // WebApplicationType:NONE, do not start the embedded WebServer, not run the web application
            // WebApplicationType:SERVLET, which starts the embedded servlet based web server
            // Webapplicationtype: reactivate. Start the embedded reactive web server. This application is a reactive web application
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            // Prepare the application context, load and execute the initialize method of all ConfigurableApplicationContext before refresh.
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // Refresh the container, initialize the ioc container, add configuration classes and components to the container, and start the automatic configuration function
            this.refreshContext(context);
            // Perform post-processing of Spring container initialization
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            // Execute callRunners and support custom run methods
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
}

4.2 SpringApplication.prepareEnvironment()

During SpringBoot startup, an Environment object will be created and initialized through the prepareEnvironment() method.

public class SpringApplication {

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		// Create Environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// Configure Environment
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		// Trigger listener: broadcast an ApplicationEnvironmentPreparedEvent event through the eventpublishingrunnlistener listener
		// After ConfigFileApplicationListener listens to this event, it will load classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config / configuration file)
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = convertEnvironment(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

	/**
	 * Create Environment
	 */
	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {// It is created according to the current application type, which is SERVLET type
		case SERVLET:
			// Load all environment variables through constructor
			return new ApplicationServletEnvironment();
		case REACTIVE:
			return new ApplicationReactiveWebEnvironment();
		default:
			return new ApplicationEnvironment();
		}
	}
}

As you can see, through the above analysis, the initialization of the Environment object is completed. The specific structure is as follows.

Keywords: Java Spring Spring Boot

Added by isam4m on Wed, 26 Jan 2022 18:52:01 +0200