关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】(上)

简介: 关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】(上)

前言


若直接提PropertyResolver或者StringValueResolver可能很小伙伴会觉得非常的陌生,但是我若提Environment和EmbeddedValueResolverAware这个感知接口,相信大部分小伙伴就能感受到一种亲切感了~。


我们的任何一个Spring Bean若实现了EmbeddedValueResolverAware接口,Spring容器在启动的时候就会自动给我们我们的Bean注入进来一个StringValueResolver。然后我们借用这个resolver就能处理一系列字符串的逻辑比如:占位符解释、SpEL计算等等~


本文就是以StringValueResolver为引子,去剖析它的底层依赖逻辑:PropertyResolver和Environment


PropertyResolver


org.springframework.core.env.PropertyResolver此接口用于在底层源之上解析一系列的属性值:例如properties文件,yaml文件,甚至是一些nosql(因为nosql也是k-v形式)。


接口中定义了一系列读取,解析,判断是否包含指定属性的方法:


// @since 3.1  出现得还是相对较晚的  毕竟SpEL也3.0之后才出来嘛~~~
public interface PropertyResolver {
  // 查看规定指定的key是否有对应的value   注意:若对应值是null的话 也是返回false
  boolean containsProperty(String key);
  // 如果没有则返回null
  @Nullable
  String getProperty(String key);
  // 如果没有则返回defaultValue
  String getProperty(String key, String defaultValue);
  // 返回指定key对应的value,会解析成指定类型。如果没有对应值则返回null(而不是抛错~)
  @Nullable
  <T> T getProperty(String key, Class<T> targetType);
  <T> T getProperty(String key, Class<T> targetType, T defaultValue);
  // 若不存在就不是返回null了  而是抛出异常~  所以不用担心返回值是null
  String getRequiredProperty(String key) throws IllegalStateException;
  <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
  // 解析${...}这种类型的占位符,把他们替换为使用getProperty方法返回的结果,解析不了并且没有默认值的占位符会被忽略(原样输出)
  String resolvePlaceholders(String text);
  // 解析不了就抛出异常~
  String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}


它的继承链可以描述如下:PropertyResolver -> ConfigurablePropertyResolver -> AbstractPropertyResolver -> PropertySourcesPropertyResolver


当然还有Environment分支,这里放在下面Environment章节继续解释说明~


ConfigurablePropertyResolver


顾名思义,它是一个可配置的处理器。这个方法不仅有父接口所有功能,还扩展定义类型转换、属性校验、前缀、后缀、分隔符等一些列的功能,这个在具体实现类里有所体现~


public interface ConfigurablePropertyResolver extends PropertyResolver {
  // 返回在解析属性时使用的ConfigurableConversionService。此方法的返回值可被用户定制化set
  // 例如可以移除或者添加Converter  cs.addConverter(new FooConverter());等等
  ConfigurableConversionService getConversionService();
  // 全部替换ConfigurableConversionService的操作(不常用)  一般还是get出来操作它内部的东东
  void setConversionService(ConfigurableConversionService conversionService);
  // 设置占位符的前缀  后缀    默认是${}
  void setPlaceholderPrefix(String placeholderPrefix);
  void setPlaceholderSuffix(String placeholderSuffix);
  // 默认值的分隔符   默认为冒号:
  void setValueSeparator(@Nullable String valueSeparator);
  // 是否忽略解析不了的占位符,默认是false  表示不忽略~~~(解析不了就抛出异常)
  void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
  /**
   * Specify which properties must be present, to be verified by
   * {@link #validateRequiredProperties()}.
   */
  void setRequiredProperties(String... requiredProperties);
  void validateRequiredProperties() throws MissingRequiredPropertiesException;
}


ConfigurableXXX成了Spring的一种命名规范,或者说是一种设计模式。它表示课配置的,所以都会提供大量的set方法


Spring很多接口都是读写分离的,最顶层接口一般都只会提供只读方法,这是Spring框架设计的一般规律之一

AbstractPropertyResolver


它是对ConfigurablePropertyResolver的一个抽象实现,实现了了所有的接口方法,并且只提供一个抽象方法给子类去实现~~~

// @since 3.1
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
  @Nullable
  private volatile ConfigurableConversionService conversionService;
  // PropertyPlaceholderHelper是一个极其独立的类,专门用来解析占位符  我们自己项目中可议拿来使用  因为它不依赖于任何其他类
  @Nullable
  private PropertyPlaceholderHelper nonStrictHelper;
  @Nullable
  private PropertyPlaceholderHelper strictHelper;
  private boolean ignoreUnresolvableNestedPlaceholders = false;
  private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
  private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
  @Nullable
  private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
  private final Set<String> requiredProperties = new LinkedHashSet<>();
  // 默认值使用的DefaultConversionService
  @Override
  public ConfigurableConversionService getConversionService() {
    // Need to provide an independent DefaultConversionService, not 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;
  }
  ... // 省略get/set
  @Override
  public void setRequiredProperties(String... requiredProperties) {
    for (String key : requiredProperties) {
      this.requiredProperties.add(key);
    }
  }
  // 校验这些key~
  @Override
  public void validateRequiredProperties() {
    MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
    for (String key : this.requiredProperties) {
      if (this.getProperty(key) == null) {
        ex.addMissingRequiredProperty(key);
      }
    }
    if (!ex.getMissingRequiredProperties().isEmpty()) {
      throw ex;
    }
  }
  ... //get/set property等方法省略  直接看处理占位符的方法即可
  @Override
  public String resolvePlaceholders(String text) {
    if (this.nonStrictHelper == null) {
      this.nonStrictHelper = createPlaceholderHelper(true);
    }
    return doResolvePlaceholders(text, this.nonStrictHelper);
  }
  private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
  }
  // 此处:最终都是委托给PropertyPlaceholderHelper去做  而getPropertyAsRawString是抽象方法  根据key返回一个字符串即可~
  private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, this::getPropertyAsRawString);
  }
}


最终,处理占位的核心逻辑在PropertyPlaceholderHelper身上,这个类不可小觑,是一个与业务无关非常强大的工具类,我们可以直接拿来主义~


PropertyPlaceholderHelper


将字符串里的占位符内容,用我们配置的properties里的替换。这个是一个单纯的类,没有继承没有实现,而且简单无依赖,没有依赖Spring框架其他的任何类。

个人感觉自己项目中可以拿来模仿或者直接使用。

// @since 3.0  Utility class for working with Strings that have placeholder values in them
public class PropertyPlaceholderHelper {
  // 这里保存着  通用的熟悉的 开闭的符号们~~~
  private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
  static {
    wellKnownSimplePrefixes.put("}", "{");
    wellKnownSimplePrefixes.put("]", "[");
    wellKnownSimplePrefixes.put(")", "(");
  }
  private final String placeholderPrefix;
  private final String placeholderSuffix;
  private final String simplePrefix;
  @Nullable
  private final String valueSeparator;
  private final boolean ignoreUnresolvablePlaceholders; // 是否采用严格模式~~
  // 从properties里取值  若你有就直接从Properties里取值了~~~
  public String replacePlaceholders(String value, final Properties properties)  {
    Assert.notNull(properties, "'properties' must not be null");
    return replacePlaceholders(value, properties::getProperty);
  }
  // @since 4.3.5   抽象类提供这个类型转换的方法~ 需要类型转换的会调用它
  // 显然它是委托给了ConversionService,而这个类在前面文章已经都重点分析过了~
  @Nullable
  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);
  }
  // 这里会使用递归,根据传入的符号,默认值等来处理~~~~
  protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { ... }
}


这个工具类不仅仅用在此处,在ServletContextPropertyUtils、SystemPropertyUtils、PropertyPlaceholderConfigurer里都是有使用到它的


PropertySourcesPropertyResolver


从上面知道AbstractPropertyResolver封装了解析占位符的具体实现。PropertySourcesPropertyResolver作为它的子类它只需要提供数据源,所以它主要是负责提供数据源。

// @since 3.1    PropertySource:就是我们所说的数据源,它是Spring一个非常重要的概念,比如可以来自Map,来自命令行、来自自定义等等~~~
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
  // 数据源们~
  @Nullable
  private final PropertySources propertySources;
  // 唯一构造函数:必须制定数据源~
  public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
    this.propertySources = propertySources;
  }
  @Override
  public boolean containsProperty(String key) {
    if (this.propertySources != null) {
      for (PropertySource<?> propertySource : this.propertySources) {
        if (propertySource.containsProperty(key)) {
          return true;
        }
      }
    }
    return false;
  }
  ...
  //最终依赖的都是propertySource.getProperty(key);方法拿到如果是字符串的话
  //就继续交给 value = resolveNestedPlaceholders((String) value);处理
  @Nullable
  protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
      for (PropertySource<?> propertySource : this.propertySources) {
        Object value = propertySource.getProperty(key);
        if (value != null) {
          // 若值是字符串,那就处理一下占位符~~~~~~  所以我们看到所有的PropertySource都是支持占位符的
          if (resolveNestedPlaceholders && value instanceof String) {
            value = resolveNestedPlaceholders((String) value);
          }
          logKeyFound(key, propertySource, value);
          return convertValueIfNecessary(value, targetValueType);
        }
      }
    }
    return null;
  }
  ...
}


ropertySourcesPropertySource属性源是Spring里一个非常重要的概念设计,涉及到Spring属性配置的非常重要的优先级关系、以及它支持的配置类型。


Environment


这个接口代表了当前应用正在运行的环境,为应用的两个重要方面建立抽象模型 【profiles】和【properties】。关于属性访问的方法通过父接口PropertyResolver暴露给客户端使用,本接口主要是扩展出访问【profiles】相关的接口。


对于他俩,我愿意这么来翻译:


  • profiles:配置。它代表应用在一启动时注册到context中bean definitions的命名的逻辑分组。
  • properties:属性。几乎在所有应用中都扮演着重要角色,他可能源自多种源头。例如属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,Map等等,Environment对象和其相关的对象一起提供给用户一个方便用来配置和解析属性的服务。

// @since 3.1   可见Spring3.x版本是Spirng一次极其重要的跨越、升级
// 它继承自PropertyResolver,所以是对属性的一个扩展~
public interface Environment extends PropertyResolver {
  // 就算被激活  也是支持同时激活多个profiles的~
  // 设置的key是:spring.profiles.active
  String[] getActiveProfiles();
  // 默认的也可以有多个  key为:spring.profiles.default
  String[] getDefaultProfiles();
  // 看看传入的profiles是否是激活的~~~~  支持!表示不激活
  @Deprecated
  boolean acceptsProfiles(String... profiles);
  // Spring5.1后提供的  用于替代上面方法   Profiles是Spring5.1才有的一个函数式接口~
  boolean acceptsProfiles(Profiles profiles);
}


我们可以通过实现接口EnvironmentAware或者直接@Autowired可以很方便的得到当前应用的环境:Environment。


稍微解释一下:若你是普通的Spring MVC环境(非Boot)也只会有一个Environment的,因为Spring容器内部会判断若你已经实例化过Environment就不会再重复实例化了~


它有如下实现:

image.png

相关文章
|
9天前
|
Java uml Spring
手写spring第四章-完善bean实例化,自动填充成员属性
手写spring第四章-完善bean实例化,自动填充成员属性
17 0
|
7天前
|
监控 Java Sentinel
Spring Cloud Sentinel:概念与实战应用
【4月更文挑战第28天】在分布式微服务架构中,确保系统的稳定性和可靠性至关重要。Spring Cloud Sentinel 为微服务提供流量控制、熔断降级和系统负载保护,有效预防服务雪崩。本篇博客深入探讨 Spring Cloud Sentinel 的核心概念,并通过实际案例展示其在项目中的应用。
16 0
|
9天前
|
XML Java 数据格式
手写spring第六章-实现应用上下文,完成bean的扩展机制
手写spring第六章-实现应用上下文,完成bean的扩展机制
14 0
|
10天前
|
canal 缓存 关系型数据库
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
|
10天前
|
存储 缓存 Java
【spring】06 循环依赖的分析与解决
【spring】06 循环依赖的分析与解决
6 1
|
10天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
11天前
|
消息中间件 Java 中间件
第十六章 Spring cloud stream应用
第十六章 Spring cloud stream应用
14 0
|
23天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
21 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
6天前
|
缓存 Java 开发者
10个点介绍SpringBoot3工作流程与核心组件源码解析
Spring Boot 是Java开发中100%会使用到的框架,开发者不仅要熟练使用,对其中的核心源码也要了解,正所谓知其然知其所以然,V 哥建议小伙伴们在学习的过程中,一定要去研读一下源码,这有助于你在开发中游刃有余。欢迎一起交流学习心得,一起成长。
|
7天前
|
安全 网络协议 Java
Netty核心NioEventLoop源码解析(下)
Netty核心NioEventLoop源码解析(下)
20 0