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

简介: 关于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的理论能够奠定一个很好的框架设计基础~

相关文章
|
7月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1193 3
|
6月前
|
SQL Java 数据库连接
Spring Data JPA 技术深度解析与应用指南
本文档全面介绍 Spring Data JPA 的核心概念、技术原理和实际应用。作为 Spring 生态系统中数据访问层的关键组件,Spring Data JPA 极大简化了 Java 持久层开发。本文将深入探讨其架构设计、核心接口、查询派生机制、事务管理以及与 Spring 框架的集成方式,并通过实际示例展示如何高效地使用这一技术。本文档约1500字,适合有一定 Spring 和 JPA 基础的开发者阅读。
665 0
|
5月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
385 8
|
7月前
|
Java 应用服务中间件 开发者
Spring Boot 技术详解与应用实践
本文档旨在全面介绍 Spring Boot 这一广泛应用于现代企业级应用开发的框架。内容将涵盖 Spring Boot 的核心概念、核心特性、项目自动生成与结构解析、基础功能实现(如 RESTful API、数据访问)、配置管理以及最终的构建与部署。通过本文档,读者将能够理解 Spring Boot 如何简化 Spring 应用的初始搭建和开发过程,并掌握其基本使用方法。
561 2
|
7月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
7月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
1347 10
|
8月前
|
安全 Java Nacos
0代码改动实现Spring应用数据库帐密自动轮转
Nacos作为国内被广泛使用的配置中心,已经成为应用侧的基础设施产品,近年来安全问题被更多关注,这是中国国内软件行业逐渐迈向成熟的标志,也是必经之路,Nacos提供配置加密存储-运行时轮转的核心安全能力,将在应用安全领域承担更多职责。
|
Java 应用服务中间件 数据库连接
Spring全家桶之Spring篇深度分析(一)
Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
Spring全家桶之Spring篇深度分析(一)
|
8月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1258 0
|
9月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1060 0

热门文章

最新文章

推荐镜像

更多
  • DNS