[spring 39 / 43] - spring Environment & amp; Properties: PropertySource, environment, Profile

Posted by ericorx on Thu, 23 Dec 2021 02:20:33 +0100

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.

Topics: Java Spring Spring Boot