关于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

相关文章
|
机器学习/深度学习 文字识别 监控
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
621 3
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3151 1
|
人工智能 API 开发者
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
888 27
|
供应链 项目管理 容器
深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
在当今快速变化的商业环境中,对象管理组织(OMG)推出了三种强大的建模标准:BPMN(业务流程模型和符号)、CMMN(案例管理模型和符号)和DMN(决策模型和符号)。它们分别适用于结构化流程管理、动态案例处理和规则驱动的决策制定,并能相互协作,覆盖更广泛的业务场景。BPMN通过直观符号绘制固定流程;CMMN灵活管理不确定的案例;DMN以表格形式定义清晰的决策规则。三者结合可优化企业效率与灵活性。 [阅读更多](https://example.com/blog)
深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
|
数据采集 机器学习/深度学习 存储
可穿戴设备如何重塑医疗健康:技术解析与应用实战
可穿戴设备如何重塑医疗健康:技术解析与应用实战
771 4
|
存储 弹性计算 安全
阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。
|
人工智能 自然语言处理 算法
DeepSeek大模型在客服系统中的应用场景解析
在数字化浪潮下,客户服务领域正经历深刻变革,AI技术成为提升服务效能与体验的关键。DeepSeek大模型凭借自然语言处理、语音交互及多模态技术,显著优化客服流程,提升用户满意度。它通过智能问答、多轮对话引导、多模态语音客服和情绪监测等功能,革新服务模式,实现高效应答与精准分析,推动人机协作,为企业和客户创造更大价值。
1060 5
|
机器学习/深度学习 JSON 算法
淘宝拍立淘按图搜索API接口系列的应用与数据解析
淘宝拍立淘按图搜索API接口是阿里巴巴旗下淘宝平台提供的一项基于图像识别技术的创新服务。以下是对该接口系列的应用与数据解析的详细分析
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
11月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1374 0

热门文章

最新文章

推荐镜像

更多
  • DNS