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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】(下)

EnvironmentCapable、EnvironmentAware


实现了此接口的类都应该有一个Environment类型的环境,并且可以通过getEnvironment方法取得。

我们熟知的所有的Spring应用上下文都实现了这个接口,因为ApplictionContext就实现了这个接口,表示每个应用上下文都是有自己的运行时环境的


还有HttpServletBean、GenericFilterBean它们既实现了EnvironmentCapable也实现了EnvironmentAware用于获取到这个环境对象。


ClassPathScanningCandidateComponentProvider也实现了它如下代码:

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
  ...
  @Override
  public final Environment getEnvironment() {
    if (this.environment == null) {
      this.environment = new StandardEnvironment();
    }
    return this.environment;
  }
  ...
}


StringValueResolver


分析完了PropertyResolver和Environment之后,来到我们今天的主菜:StringValueResolver。


先说说StringValueResolver本身,Spring对它的定义为:一个处理字符串的简单策略接口。

// @since 2.5  该接口非常简单,就是个函数式接口~
@FunctionalInterface
public interface StringValueResolver {
  @Nullable
  String resolveStringValue(String strVal);
}


唯一public实现类为:EmbeddedValueResolver


EmbeddedValueResolver


帮助ConfigurableBeanFactory处理placeholders占位符的。ConfigurableBeanFactory#resolveEmbeddedValue处理占位符真正干活的间接的就是它~~

// @since 4.3  这个类出现得还是蛮晚的   因为之前都是用内部类的方式实现的~~~~这个实现类是最为强大的  只是SpEL
public class EmbeddedValueResolver implements StringValueResolver {
  // BeanExpressionResolver之前有非常详细的讲解,简直不要太熟悉~  它支持的是SpEL  可以说非常的强大
  // 并且它有BeanExpressionContext就能拿到BeanFactory工厂,就能使用它的`resolveEmbeddedValue`来处理占位符~~~~
  // 双重功能都有了~~~拥有了和@Value一样的能力,非常强大~~~
  private final BeanExpressionContext exprContext;
  @Nullable
  private final BeanExpressionResolver exprResolver;
  public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
    this.exprContext = new BeanExpressionContext(beanFactory, null);
    this.exprResolver = beanFactory.getBeanExpressionResolver();
  }
  @Override
  @Nullable
  public String resolveStringValue(String strVal) {
    // 先使用Bean工厂处理占位符resolveEmbeddedValue
    String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
    // 再使用el表达式参与计算~~~~
    if (this.exprResolver != null && value != null) {
      Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
      value = (evaluated != null ? evaluated.toString() : null);
    }
    return value;
  }
}


关于Bean工厂resolveEmbeddedValue的实现,我们这里也顺带看看:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
  ...
  @Override
  @Nullable
  public String resolveEmbeddedValue(@Nullable String value) {
    if (value == null) {
      return null;
    }
    String result = value;
    // embeddedValueResolvers是个复数:因为我们可以自定义处理器添加到bean工厂来,增强它的能力
    for (StringValueResolver resolver : this.embeddedValueResolvers) {
      result = resolver.resolveStringValue(result);
      // 只要处理结果不为null,所以的处理器都会执行到~~~~
      if (result == null) {
        return null;
      }
    }
    return result;
  }
  ...
}


而Bean工厂的处理器都怎么添加进去的呢????


public abstract class AbstractApplicationContext extends DefaultResourceLoader
    implements ConfigurableApplicationContext {
  protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    ...
    // 如果从来没有注册过,Spring容器默认会给注册一个这样的内部类
    // 可以看到,它最终还是委托给了Environment去干这件事~~~~~~
    // 显然它最终就是调用PropertyResolver#resolvePlaceholders
    if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
    }
    ...
  } 
}


由此可见,解析占位符最终都返璞归真,真正最终的处理类,处理方法方法是:AbstractPropertyResolver#resolvePlaceholders,这就是我们非常熟悉了,上面也有详细讲解,最终都是委托给了PropertyPlaceholderHelper去处理的~


由此可见,若我们通过实现感知接口EmbeddedValueResolverAware得到一个StringValueResolver来处理我们的占位符、SpEL计算。根本原因是:

class ApplicationContextAwareProcessor implements BeanPostProcessor {
  // 这里new的一个EmbeddedValueResolver,它持有对beanFactory的引用~~~
  // 所以调用者直接使用的是EmbeddedValueResolver:它支持解析占位符(依赖于Enviroment上面有说到)并且支持SpEL的解析  非常强的
  public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
  }
}


Spring对这个感知接口的命名也很实在,我们通过实现EmbeddedValueResolverAware这个接口得到的实际上是一个EmbeddedValueResolver,提供处理占位符和SpEL等高级功能。


另外StringValueResolver还有个实现类是PropertyPlaceholderConfigurer的private内部类实现,PlaceholderResolvingStringValueResolver逻辑也非常的简单,此处就不展开了。

关于PropertyPlaceholderConfigurer本身,我们在当初xml导入配置的时候经常看到如下配置:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
      <!-- classpath:conf/jdbc.properties  -->
       <value>conf/jdbc.properties</value>
   </property>
    <property name="fileEncoding">
        <value>UTF-8</value>
    </property>
</bean>


而在注解时代,一般建议使用@PropertySource代替~


关于SpEL的讲解和BeanExpressionContext以及BeanExpressionResolver我之前文章有非常详细的讲解,强烈建议参考博文:

【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“


PropertyResolver的resolvePlaceholders()和getProperty()的区别

这个区别其实很多人都并不能明白,举个例子:

public class Main {
    public static void main(String[] args) {
        StandardEnvironment environment = new StandardEnvironment();
        MutablePropertySources mutablePropertySources = environment.getPropertySources();
        MapPropertySource mapPropertySource = new MapPropertySource("diy", new HashMap<String, Object>() {{
            put("app.name", "fsx");
            put("app.key", "${user.home1}"); // 注意这里是user.home1 特意让系统属性里不存在的
            put("app.full", "${app.key} + ${app.name}");
        }});
        mutablePropertySources.addFirst(mapPropertySource);
        // 正常使用
        String s = environment.resolvePlaceholders("${app.full}");
        System.out.println(s);
        s = environment.getProperty("app.full");
        System.out.println(s);
    }
}


结果为:


${user.home1} + fsx
Exception in thread "main" java.lang.IllegalArgumentException: Could not resolve placeholder 'user.home1' in value "${user.home1}"
  at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
  at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:160)


由此可以得出如下几点结论:


注意:他俩最终解析的都是${app.key} + ${app.name}这个字符串,只是两种方法get取值的方式不一样~


  1. properties里面可以书写通配符如${app.name},但需要注意:1. properties里的内容都原封不动的被放进了PropertySource里(或者说是环境里),而是只有在需要用的时候才会解析它2. 可以引用系统属性、环境变量等,设置引用被的配置文件里都是ok的(只要保证在同一Environment就成)
  2. resolvePlaceholders()它的入参是${}一起也包含进来的。它有如下特点:1. 若${}里面的key不存在,就原样输出,不报错。若存在就使用值替换2. key必须用${}包着,否则原样输出~~3. 若是resolveRequiredPlaceholders()方法,那key不存在就会抛错~
  3. getProperty()指定的是key本身,并不需要包含${},1. 若key不存在返回null,但是若key的值里还有占位符,那就就继续解析。若出现占位符里的key不存在时,就抛错2. getRequiredProperty()方法若key不存在就直接报错了~


注意:@Value注解我们一般这么使用@Value("${app.full}")来读取配置文件里的值,所以它即使出现了如上占位符不存在也原样输出不会报错(当然你的key必须存在啊),因为已经对@Value分析过多次:DefaultListableBeanFactory解析它的时候,最终会把表达式先交给StringValueResolver们去处理占位符,调用的就是resolver.resolveStringValue(result)方法。而最终执行它的见:


beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));


所以最终是委托给Environment的resolvePlaceholders()方法去处理的,所以由上结论可知,即是存在占位符的key不存在,原样输出即可嘛。


备注最终解析都是交给了PropertyPlaceholderHelper,它默认支持{}、[]、()等占位符。而我们最为常用的就是${},注意它的placeholderPrefix=${(而不是单单的{),后缀是}


占位符使用小技巧


例如一般我们的web程序的application.properties配置端口如下:

server.port=8080

而打包好后我们可以通过启动参数:--server.port=9090来改变此端口。但是若我们配置文件这么写:

server.port=${port:8080}


那我们启动参数可以变短了,这样写就成:--port=9090。相信了解了上面原理的小伙伴,理解这个小技巧是非常简单的事咯~~~


${port:8080}表示没有port这个key,就用8080。 有这个key就用对应的值~


总结


PropertyResolver作为一个处理底层属性源的接口,可能很少有人熟悉。但是Environment作为一个上层应用接口,代表着Spring应用运行环境属性信息,可以说还是非常的重要的。毕竟平时开发中,我们也不乏少用~


另外它和Spring的属性源:PropertySource也有非常大的关联,而属性源这个概念在任何一个框架设计中我认为都是很重要的考量,Spring、SpringBoot尤甚。因此了解了Env的理论能够奠定一个很好的框架设计基础~

相关文章
|
24天前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术深度解析:从基础到应用的全面介绍
人工智能(AI)技术的迅猛发展,正在深刻改变着我们的生活和工作方式。从自然语言处理(NLP)到机器学习,从神经网络到大型语言模型(LLM),AI技术的每一次进步都带来了前所未有的机遇和挑战。本文将从背景、历史、业务场景、Python代码示例、流程图以及如何上手等多个方面,对AI技术中的关键组件进行深度解析,为读者呈现一个全面而深入的AI技术世界。
100 10
|
16天前
|
人工智能 前端开发 Java
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
本文介绍了如何使用Spring AI Alibaba开发一个简单的AI对话应用。通过引入`spring-ai-alibaba-starter`依赖和配置API密钥,结合Spring Boot项目,只需几行代码即可实现与AI模型的交互。具体步骤包括创建Spring Boot项目、编写Controller处理对话请求以及前端页面展示对话内容。此外,文章还介绍了如何通过添加对话记忆功能,使AI能够理解上下文并进行连贯对话。最后,总结了Spring AI为Java开发者带来的便利,简化了AI应用的开发流程。
241 0
|
16天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1天前
|
缓存 Kubernetes Docker
GitLab Runner 全面解析:Kubernetes 环境下的应用
GitLab Runner 是 GitLab CI/CD 的核心组件,负责执行由 `.gitlab-ci.yml` 定义的任务。它支持多种执行方式(如 Shell、Docker、Kubernetes),可在不同环境中运行作业。本文详细介绍了 GitLab Runner 的基本概念、功能特点及使用方法,重点探讨了流水线缓存(以 Python 项目为例)和构建镜像的应用,特别是在 Kubernetes 环境中的配置与优化。通过合理配置缓存和镜像构建,能够显著提升 CI/CD 流水线的效率和可靠性,助力开发团队实现持续集成与交付的目标。
|
14天前
|
安全 API 数据安全/隐私保护
速卖通AliExpress商品详情API接口深度解析与实战应用
速卖通(AliExpress)作为全球化电商的重要平台,提供了丰富的商品资源和便捷的购物体验。为了提升用户体验和优化商品管理,速卖通开放了API接口,其中商品详情API尤为关键。本文介绍如何获取API密钥、调用商品详情API接口,并处理API响应数据,帮助开发者和商家高效利用这些工具。通过合理规划API调用策略和确保合法合规使用,开发者可以更好地获取商品信息,优化管理和营销策略。
|
1月前
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
57 14
|
1月前
|
机器学习/深度学习 搜索推荐 API
淘宝/天猫按图搜索(拍立淘)API的深度解析与应用实践
在数字化时代,电商行业迅速发展,个性化、便捷性和高效性成为消费者新需求。淘宝/天猫推出的拍立淘API,利用图像识别技术,提供精准的购物搜索体验。本文深入探讨其原理、优势、应用场景及实现方法,助力电商技术和用户体验提升。
|
1月前
|
XML 前端开发 安全
Spring MVC:深入理解与应用实践
Spring MVC是Spring框架提供的一个用于构建Web应用程序的Model-View-Controller(MVC)实现。它通过分离业务逻辑、数据、显示来组织代码,使得Web应用程序的开发变得更加简洁和高效。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring MVC,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
81 2
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
96 2
|
16天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

推荐镜像

更多
下一篇
开通oss服务