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

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

前言


本文主要从PropertiesLoaderSupport和PropertyResourceConfigurer出发,聊聊由它衍生出来的一些实用API。(和加载Properties有关)

Spring的PropertyResourceConfigurer是个抽象类,继承自PropertiesLoaderSupport,并实现了接口BeanFactoryPostProcessor。


此处注意:它是个Bean工厂的后置处理器,而不是Bean的后置处理器


它抽象了容器启动时,BeanFactory后置处理阶段对容器中所有bean定义中的属性进行配置的一般逻辑,属性配置所使用的属性来源是基类PropertiesLoaderSupport方法所规定的那些属性。


从命名中就能看出,它和PropertyResource有关,且和Bean工厂相关~~


PropertiesLoaderSupport


org.springframework.core.io.support.PropertiesLoaderSupport是一个抽象基类,它抽象了从不同渠道加载属性的通用逻辑,以及这些属性应用优先级上的一些考虑,它所提供的这些功能主要供实现子类使用。


它将属性分成两类:


  1. 本地属性(也叫缺省属性):直接以Properties对象形式设置进来的属性
  2. 外来属性:通过外部资源Resource形式设置进来需要加载的那些属性


对于本地属性和外来属性之间的的使用优先级,通过属性localOverride来标识。如果localOverride为false,表示外部属性优先级高,这也是缺省设置。如果localOverride为true,表示本地属性优先级高。


它还有一个属性fileEncoding用来表示从属性文件加载属性时使用的字符集。(毕竟Properties文件存在乱码问题)

// @since 1.2.2
public abstract class PropertiesLoaderSupport {
  @Nullable
  protected Properties[] localProperties;
  protected boolean localOverride = false;
  // 外部配置 Resource[]
  @Nullable
  private Resource[] locations;
  private boolean ignoreResourceNotFound = false;
  @Nullable
  private String fileEncoding;
  // 从流中读取 Properties / xml  // 外来属性加载工具
  private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
  public void setProperties(Properties properties) {
    this.localProperties = new Properties[] {properties};
  }
  public void setPropertiesArray(Properties... propertiesArray) {
    this.localProperties = propertiesArray;
  }
  public void setLocation(Resource location) {
    this.locations = new Resource[] {location};
  }
  public void setLocations(Resource... locations) {
    this.locations = locations;
  }
  public void setLocations(Resource... locations) {
    this.locations = locations;
  }
  ... // 省略get/set
  // 下面是提供给子类实现的方法们~~~~~~~~~
  protected Properties mergeProperties() throws IOException {
    Properties result = new Properties();
    // localOverride默认是false  若是true,提前先加载外部化配置
    if (this.localOverride) {
      // Load properties from file upfront, to let local properties override.
      loadProperties(result);
    }
    if (this.localProperties != null) {
      for (Properties localProp : this.localProperties) {
        // 厉害了 属性合并  把Properties合并进map  localProp本地会覆盖掉后面的result~~
        CollectionUtils.mergePropertiesIntoMap(localProp, result);
      }
    }
    // 若是false,再装载一次  把它放进result里面~~~外部配置覆盖当前的result嘛~
    if (!this.localOverride) {
      // Load properties from file afterwards, to let those properties override.
      loadProperties(result);
    }
    return result;
  }
  // 加载外部配置~~~~~~~ 从Resource读取进来~  借助的还是PropertiesLoaderUtils
  protected void loadProperties(Properties props) throws IOException {
    if (this.locations != null) {
      for (Resource location : this.locations) {
        PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
      }
    }
  }
}


对于该类的相关属性,都提供了对应的set方法。

PropertiesLoaderSupport所实现的功能并不多,主要是设置要使用的本地属性和外部属性文件资源路径,最终通过mergeProperties方法将这些属性合并成一个Properties对象,本地属性和外部属性之间的优先级关系由属性localOverride决定。


PropertiesLoaderSupport的直接实现子类有PropertiesFactoryBean和PropertyResourceConfigurer


PropertiesFactoryBean


实现了FactoryBean,用来生产Properties,可以配置是否单例(默认是单例)。


public class PropertiesFactoryBean extends PropertiesLoaderSupport
    implements FactoryBean<Properties>, InitializingBean {
  // 默认它是单例的
  private boolean singleton = true;
  @Nullable
  private Properties singletonInstance;
  public final void setSingleton(boolean singleton) {
    this.singleton = singleton;
  }
  ...
  // 只有singleton为true时候,才会创建一个缓存着~~~
  @Override
  public final void afterPropertiesSet() throws IOException {
    if (this.singleton) {
      this.singletonInstance = createProperties();
    }
  }
  // 通过合并本地属性  来得到一个Properties~~~~
  protected Properties createProperties() throws IOException {
    return mergeProperties();
  }
  @Override
  @Nullable
  public final Properties getObject() throws IOException {
    if (this.singleton) {
      return this.singletonInstance;
    }
    else {
      return createProperties();
    }
  }
  @Override
  public Class<Properties> getObjectType() {
    return Properties.class;
  }
}


在我们还是xml时代的时候,我们其中一种导入配置文件的方式如下(这种代码有木有一种遗失的美好的感觉):


<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
  <property name="locations"><!-- 这里是PropertiesFactoryBean类,它也有个locations属性,也是接收一个数组,跟上面一样
    <array>
      <value>classpath:jdbc.properties</value>
    </array>
  </property>
 </bean>


此处直接是向容器内放入了一个Bean,而这个Bean就是Properties类型,显然这种方式并不会加入到环境Environment里面去。so我们在使用@Value引用的时候比如使用SpEL才能引用到:


@Value("#{prop['datasource.url']}")  // 用@Value("${datasource.url}") 这样是读取不到的  此处务必要注意


附:其实xml时代还有一种常见的引用配置文件的方式如下:(Spring加载properties文件的两种方式)


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

它的原理其实是PropertyPlaceholderConfigurer,下面会说到。它等价于下面这么配置:

 <!-- 与上面的配置等价,下面的更容易理解  这个beanName写不写无所谓 -->
 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations"> <!-- PropertyPlaceholderConfigurer类中有个locations属性,接收的是一个数组,即我们可以在下面配好多个properties文件 -->
    <array>
      <value>classpath:jdbc.properties</value>
    </array>
  </property>
 </bean>

这种方式虽然也并没有加入到Environment环境抽象里面去,但我们取值仍然可以如下取值:(至于原因,小伙伴们可自行思考)

@Value("${datasource.url}") // 占位符取值即可


下面以Java配置方式示例,使用PropertiesFactoryBean加载属性配置文件:

@Configuration
public class RootConfig {
    @Bean
    public PropertiesFactoryBean prop() {
        PropertiesFactoryBean prop = new PropertiesFactoryBean();
        prop.setLocation(new ClassPathResource("jdbc.properties"));
        return prop;
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
    @Value("#{prop['datasource.url']}")
    private String url;
    @Test
    public void test1() {
        System.out.println(url); //jdbc:mysql://localhost:3306/jedi?zeroDateTimeBehavior=convertToNull
    }
}


测试通过,输出正常。


PropertyResourceConfigurer


允许从属性资源(即属性文件)配置单个bean属性值。对于以系统管理员为目标的自定义配置文件很有用,这些文件覆盖在应用程序上下文中配置的bean属性。

它是个抽象类,它的继承图谱如下:

image.png


它实现了BeanFactoryPostProcessor并且还实现了PriorityOrdered表示它的优先级是非常高的。


// @since 02.10.2003
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered {
  private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered
  // 处理每个Bean~~~
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    try {
      // 合并本地属性和外部指定的属性文件资源中的属性 
      Properties mergedProps = mergeProperties();
      // 将属性的值做转换(仅在必要的时候做)  
      convertProperties(mergedProps);
      // 对容器中的每个bean定义进行处理,也就是替换每个bean定义中的属性中的占位符  
      // 该方法为抽象方法,子类去处理~~~
      processProperties(beanFactory, mergedProps);
    } catch (IOException ex) {
      throw new BeanInitializationException("Could not load properties", ex);
    }
  }
  protected void convertProperties(Properties props) {
    Enumeration<?> propertyNames = props.propertyNames();
    while (propertyNames.hasMoreElements()) {
      String propertyName = (String) propertyNames.nextElement();
      String propertyValue = props.getProperty(propertyName);
      // convertProperty是个空实现,子类不复写就不会有转换的动作~~~~
      String convertedValue = convertProperty(propertyName, propertyValue);
      // 转成功了  说明就不会相等了  那就set进去覆盖之前的~~
      if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) {
        props.setProperty(propertyName, convertedValue);
      }
    }
  }
}


processProperties是抽象方法,留给子类去处理占位符等情况。不过其目的很明确,是对容器中每个bean定义中的属性进行处理。但具体处理是什么,就要看实现子类自身的设计目的了。

比如实现子类PropertyOverrideConfigurer和实现子类PropertyPlaceholderConfigurer就分别有自己的bean定义属性处理逻辑。

相关文章
|
5天前
|
存储 Java 数据安全/隐私保护
|
5天前
|
安全 Java 数据库连接
《Spring Boot配置文件大揭秘:看懂 application.yaml 与 bootstrap.yaml 的不同》
《Spring Boot配置文件大揭秘:看懂 application.yaml 与 bootstrap.yaml 的不同》
154 0
|
5天前
|
Java 数据库连接 API
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
57 0
|
5天前
|
XML JavaScript Java
【JavaEE】Spring Boot - 配置文件
【JavaEE】Spring Boot - 配置文件
7 0
|
5天前
|
XML Java 数据格式
Spring 属性注入方式
Spring 属性注入方式
14 2
|
5天前
|
Java 数据库连接 数据库
Spring事务简介,事务角色,事务属性
Spring事务简介,事务角色,事务属性
19 2
|
5天前
|
Java Apache Spring
Spring BeanUtils与Apache BeanUtils提供基本属性复制,适用于简单需求
【5月更文挑战第4天】Spring BeanUtils与Apache BeanUtils提供基本属性复制,适用于简单需求;Cglib BeanCopier用于转换为Cglib代理对象;Apache PropertyUtils处理属性操作;Dozer支持复杂对象映射。选择工具取决于具体需求,如需精细控制或对象映射,推荐Dozer或Apache PropertyUtils。Apache BeanUtils可能因潜在的封装性破坏被禁用。
24 3
|
5天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
27 1
|
5天前
|
JSON Java 数据库连接
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
28 1
|
5天前
|
XML Java 关系型数据库
注解驱动事务:Spring中基于注解的事务属性配置详解
注解驱动事务:Spring中基于注解的事务属性配置详解
43 0
注解驱动事务:Spring中基于注解的事务属性配置详解