详解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定义属性处理逻辑。

相关文章
|
2月前
|
XML Java 数据格式
Spring从入门到入土(xml配置文件的基础使用方式)
本文详细介绍了Spring框架中XML配置文件的使用方法,包括读取配置文件、创建带参数的构造对象、使用工厂方法和静态方法创建对象、对象生命周期管理以及单例和多例模式的测试。
119 7
Spring从入门到入土(xml配置文件的基础使用方式)
|
2月前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
21天前
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
41 8
|
3月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
305 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
132 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
3月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
3月前
|
消息中间件 NoSQL 安全
(转)Spring Boot加载 不同位置的 application.properties配置文件顺序规则
这篇文章介绍了Spring Boot加载配置文件的顺序规则,包括不同位置的application.properties文件的加载优先级,以及如何通过命令行参数或环境变量来指定配置文件的名称和位置。
|
4月前
|
Java Spring
Spring boot +Thymeleaf 本地图片加载失败(图片路径)的问题及解决方法
这篇文章详细讲解了在Spring Boot应用程序中本地图片无法加载的问题原因,并提供了两个示例来说明如何通过使用正确的相对路径或Thymeleaf语法来解决图片路径问题。
|
4月前
|
Java Spring 传感器
AI 浪潮席卷,Spring 框架配置文件管理与环境感知,为软件稳定护航,你还在等什么?
【8月更文挑战第31天】在软件开发中,配置文件管理至关重要。Spring框架提供强大支持,便于应对不同环境需求,如电商项目的开发、测试与生产环境。它支持多种格式的配置文件(如properties和YAML),并能根据环境加载不同配置,如数据库连接信息。通过`@Profile`注解可指定特定环境下的配置生效,同时支持通过命令行参数或环境变量覆盖配置值,确保应用稳定性和可靠性。
66 0
|
4月前
|
Java Spring 开发者
Spring 框架配置属性绑定大比拼:@Value 与 @ConfigurationProperties,谁才是真正的王者?
【8月更文挑战第31天】Spring 框架提供 `@Value` 和 `@ConfigurationProperties` 两种配置属性绑定方式。`@Value` 简单直接,适用于简单场景,但处理复杂配置时略显不足。`@ConfigurationProperties` 则以类级别绑定配置,简化代码并更好组织配置信息。本文通过示例对比两者特点,帮助开发者根据具体需求选择合适的绑定方式,实现高效且易维护的配置管理。
60 0