[死磕 Spring 39/43] --- Spring 的环境&属性:PropertySourceEnvironmentProfile

Posted wei198621

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[死磕 Spring 39/43] --- Spring 的环境&属性:PropertySourceEnvironmentProfile相关的知识,希望对你有一定的参考价值。

引用原文:
https://www.cmsblogs.com/article/1391375600689745920
[死磕 Spring 39/43] — Spring 的环境&属性:PropertySource、Environment、Profile

正文

spring.profiles.active 和 @Profile 这两个我相信各位都熟悉吧,主要功能是可以实现不同环境下(开发、测试、生产)参数配置的切换。其实关于环境的切换,小编在博客 【死磕Spring】----- IOC 之 PropertyPlaceholderConfigurer 的应用 已经介绍了利用 PropertyPlaceholderConfigurer 来实现动态切换配置环境,当然这种方法需要我们自己实现,有点儿麻烦。但是对于这种非常实际的需求,Spring 怎么可能没有提供呢?下面小编就问题来对 Spring 的环境 & 属性来做一个分析说明。

概括

Spring 环境 & 属性由四个部分组成:PropertySource、PropertyResolver、Profile 和 Environment。

  • PropertySource:属性源,key-value 属性对抽象,用于配置数据。
  • PropertyResolver:属性解析器,用于解析属性配置
  • Profile:剖面,只有激活的剖面的组件/配置才会注册到 Spring 容器,类似于 Spring Boot 中的 profile
  • Environment:环境,Profile 和 PropertyResolver 的组合。

下面是整个体系的结构图:

Properties

下图是 PropertyResolver 体系结构图:

  • PropertyResolver :
  • ConfigurablePropertyResolver:供属性类型转换的功能
  • AbstractPropertyResolver:解析属性文件的抽象基类
  • PropertySourcesPropertyResolver:PropertyResolver 的实现者,他对一组 PropertySources 提供属性解析服务

PropertyResolver

属性解析器,用于解析任何基础源的属性的接口
    public interface PropertyResolver 
    
        // 是否包含某个属性
        boolean containsProperty(String key);
    
        // 获取属性值 如果找不到返回null
        @Nullable
        String getProperty(String key);
    
        // 获取属性值,如果找不到返回默认值  
        String getProperty(String key, String defaultValue);
    
        // 获取指定类型的属性值,找不到返回null
        @Nullable
        <T> T getProperty(String key, Class<T> targetType);
    
        // 获取指定类型的属性值,找不到返回默认值
        <T> T getProperty(String key, Class<T> targetType, T defaultValue);
    
        // 获取属性值,找不到抛出异常IllegalStateException
        String getRequiredProperty(String key) throws IllegalStateException;
    
        // 获取指定类型的属性值,找不到抛出异常IllegalStateException
        <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
    
        // 替换文本中的占位符($key)到属性值,找不到不解析
        String resolvePlaceholders(String text);
    
        // 替换文本中的占位符($key)到属性值,找不到抛出异常IllegalArgumentException
        String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
    

从 API 上面我们就知道属性解析器 PropertyResolver 的作用了。下面是一个简单的运用。

    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

提供属性类型转换的功能

通俗点说就是 ConfigurablePropertyResolver 提供属性值类型转换所需要的 ConversionService。

    public interface ConfigurablePropertyResolver extends PropertyResolver 
    
        // 返回执行类型转换时使用的 ConfigurableConversionService
        ConfigurableConversionService getConversionService();
    
        // 设置 ConfigurableConversionService
        void setConversionService(ConfigurableConversionService conversionService);
    
        // 设置占位符前缀
        void setPlaceholderPrefix(String placeholderPrefix);
    
        // 设置占位符后缀
        void setPlaceholderSuffix(String placeholderSuffix);
    
        // 设置占位符与默认值之间的分隔符
        void setValueSeparator(@Nullable String valueSeparator);
    
        // 设置当遇到嵌套在给定属性值内的不可解析的占位符时是否抛出异常
        // 当属性值包含不可解析的占位符时,getProperty(String)及其变体的实现必须检查此处设置的值以确定正确的行为。
        void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
    
        // 指定必须存在哪些属性,以便由validateRequiredProperties()验证
        void setRequiredProperties(String... requiredProperties);
    
        // 验证setRequiredProperties指定的每个属性是否存在并解析为非null值
        void validateRequiredProperties() throws MissingRequiredPropertiesException;
    
    

从 ConfigurablePropertyResolver 所提供的方法来看,除了访问和设置 ConversionService 外,主要还提供了一些解析规则之类的方法。

就 Properties 体系而言,PropertyResolver 定义了访问 Properties 属性值的方法,而 ConfigurablePropertyResolver 则定义了解析 Properties 一些相关的规则和值进行类型转换所需要的 Service。该体系有两个实现者:AbstractPropertyResolver 和 PropertySourcesPropertyResolver,其中 AbstractPropertyResolver 为实现的抽象基类,PropertySourcesPropertyResolver 为真正的实现者。

AbstractPropertyResolver

解析属性文件的抽象基类

AbstractPropertyResolver 作为基类它仅仅只是设置了一些解析属性文件所需要配置或者转换器,如 setConversionService()、setPlaceholderPrefix()、setValueSeparator(),其实这些方法的实现都比较简单都是设置或者获取 AbstractPropertyResolver 所提供的属性,如下:

    // 类型转换去
    private volatile ConfigurableConversionService conversionService;
    // 占位符
    private PropertyPlaceholderHelper nonStrictHelper;
    //
    private PropertyPlaceholderHelper strictHelper;
    // 设置是否抛出异常
    private boolean ignoreUnresolvableNestedPlaceholders = false;
    // 占位符前缀
    private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
    // 占位符后缀
    private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
    // 与默认值的分割
    private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
    // 必须要有的字段值
    private final Set<String> requiredProperties = new LinkedHashSet<>();

这些属性都是 ConfigurablePropertyResolver 接口所提供方法需要的属性,他所提供的方法都是设置和读取这些值,如下几个方法:

        public ConfigurableConversionService getConversionService() 
            // 需要提供独立的DefaultConversionService,而不是PropertySourcesPropertyResolver 使用的共享DefaultConversionService。
            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;
        

而对属性的访问则委托给子类 PropertySourcesPropertyResolver 实现。

        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 的实现者,他对一组 PropertySources 提供属性解析服务

它仅有一个成员变量:PropertySources。该成员变量内部存储着一组 PropertySource,表示 key-value 键值对的源的抽象基类,即一个 PropertySource 对象则是一个 key-value 键值对。如下:

    public abstract class PropertySource<T> 
        protected final Log logger = LogFactory.getLog(getClass());
        protected final String name;
        protected final T source;
    
       //......
    

对外公开的 getProperty() 都是委托给 getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) 实现,他有三个参数,分别表示为:

  • key:获取的 key
  • targetValueType: 目标 value 的类型
  • resolveNestedPlaceholders:是否解决嵌套占位符

源码如下:

        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;
        

首先从 propertySource 中获取指定 key 的 value 值,然后判断是否需要进行嵌套占位符解析,如果需要则调用 resolveNestedPlaceholders() 进行嵌套占位符解析,然后调用 convertValueIfNecessary() 进行类型转换。

resolveNestedPlaceholders()

该方法用于解析给定字符串中的占位符,同时根据 ignoreUnresolvableNestedPlaceholders 的值,来确定是否对不可解析的占位符的处理方法:是忽略还是抛出异常(该值由 setIgnoreUnresolvableNestedPlaceholders() 设置)。

        protected String resolveNestedPlaceholders(String value) 
            return (this.ignoreUnresolvableNestedPlaceholders ?
                    resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
        

如果 this.ignoreUnresolvableNestedPlaceholders 为 true,则调用 resolvePlaceholders() ,否则调用 resolveRequiredPlaceholders() 但是无论是哪个方法,最终都会到 doResolvePlaceholders(),该方法接收两个参数:

  • String 类型的 text:待解析的字符串
  • PropertyPlaceholderHelper 类型的 helper:用于解析占位符的工具类。
        private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) 
            return helper.replacePlaceholders(text, this::getPropertyAsRawString);
        

PropertyPlaceholderHelper 是用于处理包含占位符值的字符串,构造该实例需要四个参数:

  • placeholderPrefix:占位符前缀
  • placeholderSuffix:占位符后缀
  • valueSeparator:占位符变量与关联的默认值之间的分隔符
  • ignoreUnresolvablePlaceholders:指示是否忽略不可解析的占位符(true)或抛出异常(false)

构造函数如下:

        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;
        

就 PropertySourcesPropertyResolver 而言,其父类 AbstractPropertyResolver 已经对上述四个值做了定义:placeholderPrefix 为 $,placeholderSuffix 为 ,valueSeparator 为 :,ignoreUnresolvablePlaceholders 默认为 false,当然我们也可以使用相应的 setter 方法自定义。

调用 PropertyPlaceholderHelper 的 replacePlaceholders() 对占位符进行处理,该方法接收两个参数,一个是待解析的字符串 value ,一个是 PlaceholderResolver 类型的 placeholderResolver,他是定义占位符解析的策略类。如下:

        public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) 
            Assert.notNull(value, "'value' must not be null");
            return parseStringValue(value, placeholderResolver, new HashSet<>());
        

内部委托给 parseStringValue() 实现:

        protected String parseStringValue(
                String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) 
    
            StringBuilder result = new StringBuilder(value);
    
            // 检索前缀,$
            int startIndex = value.indexOf(this.placeholderPrefix);
            while (startIndex != -1) 
                // 检索后缀 ,
                int endIndex = findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) 
                    // 前缀和后缀之间的字符串
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    // 循环占位符
                    // 判断该占位符是否已经处理了
                    if (!visitedPlaceholders.add(originalPlaceholder)) 
                        throw new IllegalArgumentException(
                                "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                    
                    // 递归调用,解析占位符
                    placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                    // 获取值
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                    // propval 为空,则提取默认值
                    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) 
                        // 递归调用,解析先前解析的占位符值中包含的占位符
                        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.以上是关于[死磕 Spring 39/43] --- Spring 的环境&amp;属性:PropertySourceEnvironmentProfile的主要内容,如果未能解决你的问题,请参考以下文章

[死磕 Spring ] ---苟声汇总

死磕spring源码spring配置文件的加载流程

[死磕 Spring 4/43] --- IOC 之 获取验证模型

死磕Spring之AOP篇

死磕 Spring----- Spring 的环境&属性:PropertySourceEnvironmentProfile

死磕 Spring----- Spring 的环境&属性:PropertySourceEnvironmentProfile