详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用【享学Spring】(中)

简介: 详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用【享学Spring】(中)

PlaceholderConfigurerSupport


它是一个抽象类,抽象基类,抽象了bean定义属性值中的占位符解析的功能,它继承自PropertyResourceConfigurer。从此抽象类命名就能看出,它的子类们肯定都和Placeholder处理占位符有关。


它的父类已经定义了后置处理阶段对容器中所有bean定义属性进行处理。PlaceholderConfigurerSupport则进一步的约定了要处理的占位符形式。同时提供了进行处理所需的一些属性(占位符前后缀等),以及一些工具方法。


实现子类会从一个属性源org.springframework.core.env.PropertySource里"拉取(pull)"属性值,然后替换处理Bean


//  它实现了BeanFactoryAware 接口  所以它知道自己的容器是谁。
public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer implements BeanNameAware, BeanFactoryAware {
  // 这三个符号已经非常熟悉了~  参考:AbstractPropertyResolver
  public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
  public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
  public static final String DEFAULT_VALUE_SEPARATOR = ":";
  protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
  protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
  @Nullable
  protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
  // 默认不trim(其实我建议trim 否则太容器出错了)
  protected boolean trimValues = false;
  @Nullable
  protected String nullValue;
  protected boolean ignoreUnresolvablePlaceholders = false;
  @Nullable
  private String beanName;
  @Nullable
  private BeanFactory beanFactory;
  ... // 生路所有的get/set方法~
  // 它并没有直接实现父类的方法processProperties,而是提供了这个do方法供子类使用~
  // 注意此处入参:要求传入一个StringValueResolver~
  protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
    // BeanDefinitionVisitor:遍历Bean的各个属性,用properties填充
    // 它会将替换的操作委托给内部的一个StringValueResolver来执行
    // 关于StringValueResolver这个上篇博文有详细讲解,出门右拐就到~
    // 此处是唯一使用BeanDefinitionVisitor这个类的地方~~~~
    BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
    // 获取容器中所有bean的名称
    String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
    for (String curName : beanNames) {
      // Check that we're not parsing our own bean definition,
      // to avoid failing on unresolvable placeholders in properties file locations.
      // 确定处理的Bean不是自己,且保证自己只处理自己所在bean工厂里面的bean定义们~~别的工厂关我啥事呢~
      if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
        BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
        try {
          // 对bean定义bd进行属性值占位符解析
          visitor.visitBeanDefinition(bd);
        } catch (Exception ex) {
          throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
        }
      }
    }
    // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
    // 使用StringValueResolver处理一下别名~
    beanFactoryToProcess.resolveAliases(valueResolver);
    // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
    // 把此处理器也加入到Bean工厂里吧~~~~ 赋能bean工厂  使用过的处理器都加进来
    beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
  }
}


缺省的占位符格式为${…},遵从Ant/Log4J/JSP EL风格


PlaceholderConfigurerSupport仍未对父类PropertyResourceConfigurer定义的抽象方法processProperties提供实现。换句话讲,如果想使用PlaceholderConfigurerSupport的能力的话,需要继续提供实现子类。


其实PlaceholderConfigurerSupport它的子类们它最多的应用场景是处理这种配置场景:


<bean id="dataSourceDefault" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>


启动容器时,初始化bean时,${key}就会替换成properties文件中的值。一般应用于基于xml配置中~


下面介绍Spring框架最常用的两种处理Properties文件的实现类:PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer。


Spring 对于Properties的操作都是分别基于上面两个类,而且两个类的实现方式是不一样的。(当然还有上面说的PropertiesFactoryBean,但它的方式不太一样~~)


PropertyPlaceholderConfigurer(重要)

这个类应该是N多个小伙伴最初使用Spring时候最初接触到的类。做个粗略的介绍;比如:


<context:property-placeholder location="classpath:jdbc.properties"/>


它的原理就是使用的PropertyPlaceholderConfigurer。当然我们也可以自己定义一个(或者多个)这个Bean。


// @since 02.10.2003  第一版就有了
public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport {
  public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
  public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
  public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
  //constants它把上面那三个常量
  private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class);
  // 默认的mode模式:Check system properties if not resolvable in the specified properties.
  private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
  // spring.getenv.ignore=true就不会再去系统属性找了,否则没有找到还会去systemEnv里面去找的~~~~  默认是会去找的
  private boolean searchSystemEnvironment = !SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME);
  // 使用constants的好处就是,让string能像类似枚举一样使用~
  public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException {
    this.systemPropertiesMode = constants.asNumber(constantName).intValue();
  }
  public void setSystemPropertiesMode(int systemPropertiesMode) {
    this.systemPropertiesMode = systemPropertiesMode;
  }
  // 处理占位符~~~~  值都从Properties里来  可以指定systemPropertiesMode
  // placeholder比如:${user.home}
  @Nullable
  protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
    String propVal = null;
    if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
      propVal = resolveSystemProperty(placeholder);
    }
    if (propVal == null) {
      propVal = resolvePlaceholder(placeholder, props);
    }
    // 是否fallback继续去  System.getProperty和System.getenv
    if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
      propVal = resolveSystemProperty(placeholder);
    }
    return propVal;
  }
  @Nullable
  protected String resolvePlaceholder(String placeholder, Properties props) {
    return props.getProperty(placeholder);
  }
  // 去系统里拿。含有System或者SystemEnv   env是可选的  但默认是true
  @Nullable
  protected String resolveSystemProperty(String key) {
    try {
      String value = System.getProperty(key);
      if (value == null && this.searchSystemEnvironment) {
        value = System.getenv(key);
      }
      return value;
    } catch (Throwable ex) {
      if (logger.isDebugEnabled()) {
        logger.debug("Could not access system property '" + key + "': " + ex);
      }
      return null;
    }
  }
  // 这个是实现父类的方法~~~~~~~~~~~~~~~~~~~~~~
  // 此处StringValueResolver是用的是PlaceholderResolvingStringValueResolver  它是一个内部类
  // 内部列实现的这个没有EmbeddedValueResolver强大,它不支持SpEL,只是从Properties里取值而已~~~
  @Override
  protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
    StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
    doProcessProperties(beanFactoryToProcess, valueResolver);
  }
}


给一个示例Demo:

@Configuration
public class RootConfig {
    @Bean
    public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer1() {
        PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
        configurer.setOrder(Ordered.HIGHEST_PRECEDENCE);
        configurer.setLocation(new ClassPathResource("beaninfo.properties"));
        return configurer;
    }
    @Scope("${bean.scope}") // 这里是能够使用占位符的
    @Bean //@Bean("${bean.beanName}") 注意这里是不能解析的
    public Person person(){
        Person person = new Person();
        return person;
    }
}

其实,这个在注解驱动的应用中,使用场景已经非常少了,在xml时代异常的重要。


需要注意的是:若你配置了多个PropertyPlaceholderConfigurer,请设置它的Order属性来控制顺序(因为它是Bean工厂后置处理器)

但是强烈建议只配置一个即可,毕竟每次它都拿所有的Bean处理,配置多个会拖慢启动速度(不同容器内除外,需要多个~)


注意,注意,注意:这种加载配置文件的方式,应用启动完就"消失了",并不会留存在Environment里的,请务必注意。

所以为了保存下他们,我们之前经常可看到如下代码:

public class PropertyPlaceholder extends PropertyPlaceholderConfigurer {
    private static Map<String,String> propertyMap;
    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
        super.processProperties(beanFactoryToProcess, props);
        propertyMap = new HashMap<String, String>();
        for (Object key : props.keySet()) {
            String keyStr = key.toString();
            String value = props.getProperty(keyStr);
            propertyMap.put(keyStr, value);
        }
    }
    //自定义一个方法,即根据key拿属性值,方便java代码中取属性值
    public static String getProperty(String name) {
        return propertyMap.get(name);
    }
}


这个自定义的PropertyPlaceholder的作用就是把配置都保存下来,并提供static方法供后续获取使用。


另外PropertyPlaceholderConfigurer有个子类–>PreferencesPlaceholderConfigurer。它是对父类的增强,它能解决如下两个可能问题:


  1. 配置文件不能放在主目录中,因为某些OS(如Win9X)没有主目录的概念
  2. 没有标准的文件命名规则,存在文件名冲突的可能性


使用java.util.prefs.Preferences能解决这个问题。因为绝大多数情况下我们的配置文件都是跟着项目走的,所以使用PropertyPlaceholderConfigurer也没问题,但是仅尽显逼格可完全使用子类PreferencesPlaceholderConfigurer,它拥有父类所有功能且更加强大~


PropertySourcesPlaceholderConfigurer


在Spring3.1之后,我建议使用PropertySourcesPlaceholderConfigurer来取代PropertyPlaceholderConfigurer。

因为汇聚了Environment、多个PropertySource;所以它能够控制取值优先级、顺序,并且还提供了访问的方法,后期再想获取也不成问题

// @since 3.1  实现了EnvironmentAware 接口可以得到自己所在的环境
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
  // 本地的
  public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";
  // 环境信息
  public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";
  // 这三个哥们  到此处应该已经非常熟悉了
  @Nullable
  private MutablePropertySources propertySources; // 注意此处:它只表示当前的环境持有的~~~~
  @Nullable
  private PropertySources appliedPropertySources;
  @Nullable
  private Environment environment; // 当前bean所处的环境~
  // 显然,~~并不建议直接set
  public void setPropertySources(PropertySources propertySources) {
    this.propertySources = new MutablePropertySources(propertySources);
  }
  // 此处:它完全重写了Bean工厂后置处理器的处理方法~~~~~
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // 若propertySources还没构造,就先构造一个出来~~~~~
    if (this.propertySources == null) {
      this.propertySources = new MutablePropertySources();
      if (this.environment != null) {
        // 此处把当前环境都放进去了,所以占位符可以使用当前环境Environment内的任何key了~~~~
        this.propertySources.addLast(
          new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
            @Override
            @Nullable
            public String getProperty(String key) {
              return this.source.getProperty(key);
            }
          }
        );
      }
      try {
        // 把本地的也作为一个source加进去  注意此处可能是addFirst和addLast~~~
        // key为:localProperties
        PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
        if (this.localOverride) {
          this.propertySources.addFirst(localPropertySource);
        }
        else {
          this.propertySources.addLast(localPropertySource);
        }
      }
      catch (IOException ex) {
        throw new BeanInitializationException("Could not load properties", ex);
      }
    }
    processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
    // 表示最终生效的 propertySources
    this.appliedPropertySources = this.propertySources;
  }
  protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
      final ConfigurablePropertyResolver propertyResolver) throws BeansException {
    // 设置ConfigurablePropertyResolver的几大参数~~~
    propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
    propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
    propertyResolver.setValueSeparator(this.valueSeparator);
    // 使用lambda表达式创建一个StringValueResolver~~~~
    StringValueResolver valueResolver = strVal -> {
      // 解析占位符~~~~~ 此处只能解析占位符
      String resolved = (this.ignoreUnresolvablePlaceholders ?
          propertyResolver.resolvePlaceholders(strVal) :
          propertyResolver.resolveRequiredPlaceholders(strVal));
      if (this.trimValues) {
        resolved = resolved.trim();
      }
      // 返回null还是返回resolved  最后还得有个判断
      return (resolved.equals(this.nullValue) ? null : resolved);
    };
    // 调用父类的doProcessProperties  把属性扫描到Bean的身上去~~~
    // 并且我们发现 我们自定义的EmbeddedValueResolver是会被添加到bean工厂里面的
    doProcessProperties(beanFactoryToProcess, valueResolver);
  }
  @Override
  @Deprecated
  protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) {
    throw new UnsupportedOperationException(
        "Call processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver) instead");
  }
  // 外部可以获取到appliedPropertySources生效的  这个相当于把生效的给我们缓存下来了 我们并不需要自己再缓存了~~~~
  // 你若需要  可以把它放进环境了(不建议)
  public PropertySources getAppliedPropertySources() throws IllegalStateException {
    Assert.state(this.appliedPropertySources != null, "PropertySources have not yet been applied");
    return this.appliedPropertySources;
  }
}
相关文章
|
4天前
|
Java uml Spring
手写spring第四章-完善bean实例化,自动填充成员属性
手写spring第四章-完善bean实例化,自动填充成员属性
13 0
|
4月前
|
XML Java 数据库
【Spring】SpringBoot 配置文件
【Spring】SpringBoot 配置文件
|
3月前
|
安全 Java 数据库连接
《Spring Boot配置文件大揭秘:看懂 application.yaml 与 bootstrap.yaml 的不同》
《Spring Boot配置文件大揭秘:看懂 application.yaml 与 bootstrap.yaml 的不同》
100 0
|
2月前
|
Java 数据库连接 API
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
49 0
|
18天前
|
JSON Java 数据库连接
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
28 1
|
18天前
|
Java Shell 测试技术
一次配置,多场景适用:Spring Boot多套配置文件的深度剖析
一次配置,多场景适用:Spring Boot多套配置文件的深度剖析
35 0
一次配置,多场景适用:Spring Boot多套配置文件的深度剖析
|
4月前
|
Java API Nacos
spring.config.import 是一个 Spring Cloud Config Server 的属性,
spring.config.import 是一个 Spring Cloud Config Server 的属性,【1月更文挑战第25天】【1月更文挑战第123篇】
56 1
|
4月前
|
缓存 Java Spring
Spring5源码(25)-Spring填充bean属性及应用生命周期接口
Spring5源码(25)-Spring填充bean属性及应用生命周期接口
39 0
|
4月前
|
XML Java 数据格式
Spring5源码(6)-Spring注入集合属性
Spring5源码(6)-Spring注入集合属性
14 0
|
4月前
|
Java Spring 容器
面试题:在spring框架下面,Bean的属性lazy-init有什么作用,默认值是多少
面试题:在spring框架下面,Bean的属性lazy-init有什么作用,默认值是多少
17 0