Quote the original text:
https://www.cmsblogs.com/article/1391375600689745920
[Spring 39/43] - Environment & Properties of Spring: PropertySource, environment, Profile
text
Spring. profiles. I believe you are familiar with active and @ Profile, The main function is to switch the parameter configuration in different environments (development, testing and production). In fact, the environment switching is written in the blog [stumbling Spring] - the application of IOC's PropertyPlaceholderConfigurer has introduced the use of PropertyPlaceholderConfigurer to dynamically switch the configuration environment. Of course, this method needs to be implemented by ourselves, which is a little troublesome. But how can Spring not provide this very practical requirement? The following small series will describe the Spring environment& Property to make an analysis description.
generalization
Spring Environment & properties are composed of four parts: PropertySource, PropertyResolver, Profile and environment.
- PropertySource: property source, abstracted from the key value property pair, used to configure data.
- PropertyResolver: Property resolver, used to resolve property configuration
- Profile: profile. Only the components / configurations of the active profile will be registered in the Spring container, similar to the profile in Spring Boot
- Environment: a combination of environment, Profile and PropertyResolver.
The following is the structure diagram of the whole system:
Properties
The following figure shows the architecture of PropertyResolver:
- PropertyResolver :
- ConfigurablePropertyResolver: function for property type conversion
- AbstractPropertyResolver: an abstract base class that resolves property files
- PropertySourcesPropertyResolver: the implementer of PropertyResolver, which provides property resolution services for a group of PropertySources
PropertyResolver
Property parser, an interface for parsing the properties of any underlying source
public interface PropertyResolver { // Include a property boolean containsProperty(String key); // Gets the property value. If it is not found, null is returned @Nullable String getProperty(String key); // Gets the property value. If it is not found, the default value is returned String getProperty(String key, String defaultValue); // Gets the property value of the specified type. If it is not found, null is returned @Nullable <T> T getProperty(String key, Class<T> targetType); // Gets the property value of the specified type. The default value is returned if it is not found <T> T getProperty(String key, Class<T> targetType, T defaultValue); // Get property value, throw exception IllegalStateException not found String getRequiredProperty(String key) throws IllegalStateException; // Gets the property value of the specified type. The exception IllegalStateException was not found <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException; // Replace the placeholder (${key}) in the text to the attribute value, which cannot be found and cannot be parsed String resolvePlaceholders(String text); // Replace the placeholder (${key}) in the text to the attribute value, and throw the exception IllegalArgumentException when it is not found String resolveRequiredPlaceholders(String text) throws IllegalArgumentException; }
From the API, we know the function of the property parser PropertyResolver. Here is a simple application.
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); System.out.println(propertyResolver.getProperty("name")); System.out.println(propertyResolver.getProperty("name", "chenssy")); System.out.println(propertyResolver.resolvePlaceholders("my name is ${name}"));
ConfigurablePropertyResolver
Provides the function of attribute type conversion
Generally speaking, ConfigurablePropertyResolver provides the ConversionService required for property value type conversion.
public interface ConfigurablePropertyResolver extends PropertyResolver { // Returns the ConfigurableConversionService used when performing type conversion ConfigurableConversionService getConversionService(); // Set ConfigurableConversionService void setConversionService(ConfigurableConversionService conversionService); // Set placeholder prefix void setPlaceholderPrefix(String placeholderPrefix); // Set placeholder suffix void setPlaceholderSuffix(String placeholderSuffix); // Sets the separator between the placeholder and the default value void setValueSeparator(@Nullable String valueSeparator); // Sets whether an exception is thrown when an unresolved placeholder nested within a given property value is encountered // When a property value contains an unresolved placeholder, the implementation of getProperty(String) and its variants must check the value set here to determine the correct behavior. void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders); // Specify which properties must exist for validation by validateRequiredProperties() void setRequiredProperties(String... requiredProperties); // Verify that each property specified by setRequiredProperties exists and resolves to a non null value void validateRequiredProperties() throws MissingRequiredPropertiesException; }
From the methods provided by ConfigurablePropertyResolver, in addition to accessing and setting ConversionService, it also provides some methods such as parsing rules.
In terms of the Properties system, PropertyResolver defines the method to access the property value, while ConfigurablePropertyResolver defines the Service required to resolve some relevant rules and values of Properties for type conversion. The system has two implementers: AbstractPropertyResolver and PropertySourcesPropertyResolver. AbstractPropertyResolver is the abstract base class of the implementation, and PropertySourcesPropertyResolver is the real implementer.
AbstractPropertyResolver
Resolve the abstract base class of the properties file
AbstractPropertyResolver, as the base class, only sets some configurations or converters required for parsing property files, such as setConversionService(), setPlaceholderPrefix(), setValueSeparator(). In fact, the implementation of these methods is relatively simple. They all set or obtain the properties provided by AbstractPropertyResolver, as follows:
// Type conversion private volatile ConfigurableConversionService conversionService; // placeholder private PropertyPlaceholderHelper nonStrictHelper; // private PropertyPlaceholderHelper strictHelper; // Sets whether an exception is thrown private boolean ignoreUnresolvableNestedPlaceholders = false; // Placeholder prefix private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; // Placeholder suffix private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; // Split from default private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; // Required field values private final Set<String> requiredProperties = new LinkedHashSet<>();
These properties are required by the methods provided by the ConfigurablePropertyResolver interface. The methods provided are to set and read these values, as follows:
public ConfigurableConversionService getConversionService() { // A separate DefaultConversionService needs to be provided instead of the shared DefaultConversionService used by PropertySourcesPropertyResolver. ConfigurableConversionService cs = this.conversionService; if (cs == null) { synchronized (this) { cs = this.conversionService; if (cs == null) { cs = new DefaultConversionService(); this.conversionService = cs; } } } return cs; } @Override public void setConversionService(ConfigurableConversionService conversionService) { Assert.notNull(conversionService, "ConversionService must not be null"); this.conversionService = conversionService; } public void setPlaceholderPrefix(String placeholderPrefix) { Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); this.placeholderPrefix = placeholderPrefix; } public void setPlaceholderSuffix(String placeholderSuffix) { Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); this.placeholderSuffix = placeholderSuffix; }
Access to properties is delegated to the subclass PropertySourcesPropertyResolver implementation.
public String getProperty(String key) { return getProperty(key, String.class); } public String getProperty(String key, String defaultValue) { String value = getProperty(key); return (value != null ? value : defaultValue); } public <T> T getProperty(String key, Class<T> targetType, T defaultValue) { T value = getProperty(key, targetType); return (value != null ? value : defaultValue); } public String getRequiredProperty(String key) throws IllegalStateException { String value = getProperty(key); if (value == null) { throw new IllegalStateException("Required key '" + key + "' not found"); } return value; } public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException { T value = getProperty(key, valueType); if (value == null) { throw new IllegalStateException("Required key '" + key + "' not found"); } return value;
PropertySourcesPropertyResolver
PropertyResolver The implementer of a group PropertySources Provide attribute resolution service
It has only one member variable: PropertySources. A set of PropertySources are stored inside the member variable, representing the abstract base class of the source of the key value key value pair, that is, a PropertySource object is a key value key value pair. As follows:
public abstract class PropertySource<T> { protected final Log logger = LogFactory.getLog(getClass()); protected final String name; protected final T source; //...... }
The publicly exposed getProperty() is delegated to the implementation of getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders). It has three parameters, which are expressed as:
- Key: obtained key
- targetValueType: the type of target value
- resolveNestedPlaceholders: resolve nested placeholders
The source code is as follows:
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { for (PropertySource<?> propertySource : this.propertySources) { if (logger.isTraceEnabled()) { logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'"); } Object value = propertySource.getProperty(key); if (value != null) { if (resolveNestedPlaceholders && value instanceof String) { value = resolveNestedPlaceholders((String) value); } logKeyFound(key, propertySource, value); return convertValueIfNecessary(value, targetValueType); } } } if (logger.isDebugEnabled()) { logger.debug("Could not find key '" + key + "' in any property source"); } return null; }
First, get the value value of the specified key from propertySource, then determine whether nested placeholder parsing is needed. If necessary, call resolveNestedPlaceholders() to parse the nested placeholder and then call convertValueIfNecessary() for type conversion.
resolveNestedPlaceholders()
This method is used to parse placeholders in a given string, and determine whether to handle non resolvable placeholders according to the value of ignoreunresolved nestedplaceholders: ignore or throw an exception (this value is set by setignoreunresolved nestedplaceholders()).
protected String resolveNestedPlaceholders(String value) { return (this.ignoreUnresolvableNestedPlaceholders ? resolvePlaceholders(value) : resolveRequiredPlaceholders(value)); }
If this If ignoreunresolved nestedplaceholders is true, resolvePlaceholders() will be called. Otherwise, resolveRequiredPlaceholders() will be called. However, no matter which method is used, it will eventually go to doResolvePlaceholders(), which receives two parameters:
- text of String type: the String to be parsed
- helper of PropertyPlaceholderHelper type: a tool class used to resolve placeholders.
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, this::getPropertyAsRawString); }
PropertyPlaceholderHelper is used to process strings containing placeholder values. Four parameters are required to construct this instance:
- placeholderPrefix: placeholder prefix
- placeholderSuffix: placeholder suffix
- valueSeparator: the separator between the placeholder variable and the associated default value
- Ignoreunresolved placeholders: indicates whether to ignore non resolvable placeholders (true) or throw exceptions (false)
The constructor is as follows:
public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) { Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); this.placeholderPrefix = placeholderPrefix; this.placeholderSuffix = placeholderSuffix; String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { this.simplePrefix = simplePrefixForSuffix; } else { this.simplePrefix = this.placeholderPrefix; } this.valueSeparator = valueSeparator; this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; }
As for PropertySourcesPropertyResolver, its parent class AbstractPropertyResolver has defined the above four values: placeholderPrefix is ${, placeholderSuffix is}, valueSeparator is:, ignoreunresolved placeholders is false by default. Of course, we can also use the corresponding setter method to customize.
Call replacePlaceholders() of PropertyPlaceholderHelper to process placeholders. This method receives two parameters, one is the string value to be resolved, and the other is the placeholderResolver of placeholderResolver type, which is the policy class that defines placeholder resolution. As follows:
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "'value' must not be null"); return parseStringValue(value, placeholderResolver, new HashSet<>()); }
Internally delegated to parseStringValue():
protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(value); // Retrieve prefix${ int startIndex = value.indexOf(this.placeholderPrefix); while (startIndex != -1) { // Retrieve suffix,} int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { // String between prefix and suffix String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; // Circular placeholder // Determine whether the placeholder has been processed if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // Recursive call, parsing placeholders placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Get value String propVal = placeholderResolver.resolvePlaceholder(placeholder); // If propval is empty, the default value is extracted if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // Recursive call to resolve placeholders contained in previously resolved placeholder values 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(); }
In fact, it is to obtain the value in the middle of the placeholder ${}, which will involve a recursive process, because this may happen. KaTeX parse error: Expected '}', got 'EOF' at end of input: {{name}}.
convertValueIfNecessary()
Whether this method is very familiar or not, this method is to complete type conversion. As follows:
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) { if (targetType == null) { return (T) value; } ConversionService conversionServiceToUse = this.conversionService; if (conversionServiceToUse == null) { // Avoid initialization of shared DefaultConversionService if // no standard type conversion is needed in the first place... if (ClassUtils.isAssignableValue(targetType, value)) { return (T) value; } conversionServiceToUse = DefaultConversionService.getSharedInstance(); } return conversionServiceToUse.convert(value, targetType); }
First, get the type conversion service conversionService, if it is empty, determine whether it can be set by reflection, if it can, turn it back directly, otherwise construct a DefaultConversionService instance, and then call convert() to complete the type conversion, followed by the Spring type conversion system. If it is not known, You can refer to Xiaobian's blog: [stumbling spring] - IOC's in-depth analysis of Bean's type conversion system
Environment
Indicates the environment in which the current application is running
The environment of an application has two key aspects: profile and properties.
- The method of properties is defined by PropertyResolver.
- Profile indicates the current running environment. Not all properties in the application will be loaded into the system. Only its properties and profile will be activated and loaded all the time,
Therefore, the function of the Environment object is to determine which configuration files (if any) are currently active and which configuration files are active by default (if any) should be active. Properties plays an important role in almost all applications and comes from a variety of sources: property files, JVM system properties, system Environment variables, JNDI, servlet context parameters, ad-hoc property objects, mappings, etc. at the same time, it inherits the PropertyResolver interface, so the Environment objects related to properties are mainly It is a convenient service interface for users to configure attribute sources and resolve attributes from attribute sources.
public interface Environment extends PropertyResolver { // Returns the set of profiles active in this environment String[] getActiveProfiles(); // If the activation profile is not set, the default set of activated profiles is returned String[] getDefaultProfiles(); boolean acceptsProfiles(String... profiles); }
The architecture diagram of Environment is as follows:
- PropertyResolver: provides property access
- Environment: provides the function of accessing and judging profiles
- ConfigurableEnvironment: provides the function of setting the active profile and default profile, as well as tools for operating Properties
- Configurable Web Environment: provides the function of configuring Servlet context and Servlet parameters
- AbstractEnvironment: it implements the ConfigurableEnvironment interface, the definition of default attributes and storage containers, implements the methods of ConfigurableEnvironment, and reserves overridable extension methods for subclasses
- StandardEnvironment: it inherits from AbstractEnvironment and is the implementation of standard Environment in non Servlet(Web) Environment
- Standardservlet Environment: inherited from StandardEnvironment, the standard Environment implementation in Servlet(Web) Environment
ConfigurableEnvironment
Provide settings to activate profile And default profile Function and operation of Properties Tools for
In addition to inheriting the Environment interface, this class also inherits the ConfigurablePropertyResolver interface, so it has the function of setting profile s and operating Properties. At the same time, it also allows the client to set and verify the required Properties, customize the transformation service and other functions. As follows:
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver { // Specify the profile set in this environment void setActiveProfiles(String... profiles); // Add a profile for this environment void addActiveProfile(String profile); // Set default profile void setDefaultProfiles(String... profiles); // Returns the PropertySources for this environment MutablePropertySources getPropertySources(); // Try to return to system The value of getenv(), if failed, is returned through system Getenv (string) to access the mapping of each key Map<String, Object> getSystemEnvironment(); // Try to return to system The value of getproperties(), if failed, is returned through system Getproperties (string) to access the mapping of each key Map<String, Object> getSystemProperties(); void merge(ConfigurableEnvironment parent); }
AbstractEnvironment
Environment Basic implementation of
Allow by setting ACTIVE_PROFILES_PROPERTY_NAME and default_ PROFILES_ PROPERTY_ The name attribute specifies the activity and default profile. The main difference between subclasses is the PropertySource object they add by default. AbstractEnvironment does not add anything. Subclasses should provide property sources through a protected customizePropertySources(MutablePropertySources) hook, and clients should use configurableenvironment Getpropertysources() to customize and operate on MutablePropertySources API.
There are two pairs of variables in AbstractEnvironment, which maintain the activation and default configuration profile s. As follows:
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"; private final Set<String> activeProfiles = new LinkedHashSet<>(); public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default"; private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
Because there are many implementation methods, we only focus on two methods: setActiveProfiles() and getActiveProfiles().
setActiveProfiles()
public void setActiveProfiles(String... profiles) { Assert.notNull(profiles, "Profile array must not be null"); if (logger.isDebugEnabled()) { logger.debug("Activating profiles " + Arrays.asList(profiles)); } synchronized (this.activeProfiles) { this.activeProfiles.clear(); for (String profile : profiles) { validateProfile(profile); this.activeProfiles.add(profile); } } }
In fact, this method is to operate the activeProfiles set. It will be emptied and re added before each setting. Before adding, validateProfile() is added to check the profile added.
protected void validateProfile(String profile) { if (!StringUtils.hasText(profile)) { throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text"); } if (profile.charAt(0) == '!') { throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator"); } }
This verification process is weak, and subclasses can provide more stringent verification rules.
getActiveProfiles()
From getActiveProfiles(), we can guess the logic of this method: just get the activeProfiles collection.
public String[] getActiveProfiles() { return StringUtils.toStringArray(doGetActiveProfiles()); }
Delegate to doGetActiveProfiles():
protected Set<String> doGetActiveProfiles() { synchronized (this.activeProfiles) { if (this.activeProfiles.isEmpty()) { String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME); if (StringUtils.hasText(profiles)) { setActiveProfiles(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(profiles))); } } return this.activeProfiles; } }
If activeProfiles is empty, get spring. Com from Properties profiles. Active configuration. If it is not empty, call setActiveProfiles() to set the profile, and finally return.
Here, the whole environment & attribute has been analyzed. As for how it is combined with the application context, we will analyze it later.