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

相关文章
|
29天前
|
XML 存储 Java
Spring重要类解析
Spring重要类解析
20 0
|
27天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
64 1
|
27天前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
65 0
|
13天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
19 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
13天前
|
XML Java 数据格式
从入门到精通:Spring基础注解的全面解析
从入门到精通:Spring基础注解的全面解析
30 2
从入门到精通:Spring基础注解的全面解析
|
13天前
|
Java 数据库 Spring
切面编程的艺术:Spring动态代理解析与实战
切面编程的艺术:Spring动态代理解析与实战
26 0
切面编程的艺术:Spring动态代理解析与实战
|
13天前
|
Java 关系型数据库 MySQL
高级对象装配:解析Spring创建复杂对象的秘诀
高级对象装配:解析Spring创建复杂对象的秘诀
27 0
高级对象装配:解析Spring创建复杂对象的秘诀
|
23小时前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
9天前
yolo-world 源码解析(六)(2)
yolo-world 源码解析(六)
18 0
|
9天前
yolo-world 源码解析(六)(1)
yolo-world 源码解析(六)
12 0

推荐镜像

更多