【深入浅出Spring原理及实战】「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【深入浅出Spring原理及实战】「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机

Spring提供配置解析功能


主要有一下xml文件占位符解析和Java的属性@Value的占位符解析配置这两种场景进行分析和实现解析,如下面两种案例。



xml文件的占位符解析配置

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"  init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
复制代码



Java的属性@Value的占位符解析配置


@Value 注解值进行属性占位符解析和替换

@Value("${config}")
private String config;
复制代码



PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer


通过配置xml来实现对Classpath下的配置文件的占位符的属性进行注入,或者实现Java的属性@Value的占位符解析配置。


  • 在Spring3.1版本之前是通过PropertyPlaceholderConfigurer实现的。


  • 在Spring3.1之后则是通过PropertySourcesPlaceholderConfigurer实现的。


注意:在Spring Context 3.1或者更高版本中,缺省使用PropertySourcesPlaceholderConfigurer工具替换了PlaceholderConfigurerSupport,而<=3.0较老的Spring Context中,为了保持和之前的版本兼容,缺省还是使用PropertyPlaceholderConfigurer



PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的实现分析


  • PropertyPlaceholderConfigurer本质是基于PlaceholderConfigurerSupport实现读取配置的。


  • PropertySourcesPlaceholderConfigurerPlaceholderConfigurerSupport的特殊化实现。

下图介绍对应的配置解析的继承关系图谱。

image.png

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的执行目标


PropertyPlaceholderConfigurerPropertyPlaceholderConfigurer在使用上并无本质的区别,两者的根本目标是将配置文件生成KV对,真正的注入工作并不由它们本身执行。


PropertySourcesPlaceholderConfigurer它用于解析bean定义中的属性值,以及注解@Value的值,使用的属性来源是当前的Spring Environment对象,以及设置给自己的PropertySources对象。


Spring Boot 自动配置类 PropertyPlaceholderAutoConfiguration
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
  public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
  }
}
复制代码

PropertyPlaceholderAutoConfiguration定义一个PropertySourcesPlaceholderConfigurer bean,该bean作为一个BeanFactoryPostProcessor,会在容器启动时容器后置处理阶段执行自己的任务。


BeanFactoryPostProcessor的优先级又优于其余的Bean。因此可以实现在bean初始化之前的注入。



postProcessBeanFactory方法的执行


如果外部指定了this.propertySources, 则直接使用它,否则从当前Spring的Environment 对象和自身的 #mergeProperties 方法调用返回的 Properties 对象构建属性源对象 this.propertySources

@Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    if (this.propertySources == null) {
      this.propertySources = new MutablePropertySources();
      if (this.environment != null) {
        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 {
        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));
    this.appliedPropertySources = this.propertySources;
  }
复制代码



构造一个基于特定属性源 this.propertySources 对属性值进行解析的属性值解析器PropertySourcesPropertyResolver, 对容器中所有的 bean 定义中的属性值,构造函数参数值。

/**
   * Visit each bean definition in the given bean factory and attempt to replace ${...} property
   * placeholders with values from the given properties.
   */
  protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
      final ConfigurablePropertyResolver propertyResolver) throws BeansException {
       // 设置属性值解析器所使用的占位符格式参数,缺省为:
       // 占位符前缀 ${
    propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
       // 占位符后缀 }
    propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
       // 缺省值分隔符 :
    propertyResolver.setValueSeparator(this.valueSeparator);
       // 结合属性 this. ignoreUnresolvablePlaceholders对propertyResolver 作进一步封装,
       // 封装出来一个 StringValueResolver valueResolver,这是最终要应用的属性值解析器
    StringValueResolver valueResolver = strVal -> {
      String resolved = (this.ignoreUnresolvablePlaceholders ?
          propertyResolver.resolvePlaceholders(strVal) :
          propertyResolver.resolveRequiredPlaceholders(strVal));
      if (this.trimValues) {
        resolved = resolved.trim();
      }
      return (resolved.equals(this.nullValue) ? null : resolved);
    };
       // 调用基类PlaceholderConfigurerSupport实现的对容器中所有 bean定义进行遍历处理属性值中占位符解析的逻辑
    doProcessProperties(beanFactoryToProcess, valueResolver);
  }
复制代码

doProcessProperties的方法目的是为了添加解析器StringValueResolver

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            StringValueResolver valueResolver) {
        // ignore
        ....
        // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
        beanFactoryToProcess.resolveAliases(valueResolver);
        // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
        beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    }
复制代码

这里的ddEmbeddedValueResolver(StringValueResolver) 是为一个 LinkedList添加值。在取用的时候是优先从链表头开始取用的。 一旦发现无法找到值,直接就抛异常了。这个就对外体现出 PropertySourcesPlaceholderConfigurer 的唯一性。


然而Spring内部还是有多个PropertySourcesPlaceholderConfigurer, 只不过除了排列在队首的 PropertySourcesPlaceholderConfigurer之外全都被忽略掉了。




PropertySourcesPlaceholderConfigurer属性注入的原理


AbstractApplicationContext#obtainFreshBeanFactory


Spring框架进行植入元素注入时机


针对于元素的注入依赖于 AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues1。



AbstractApplicationContext#finishBeanFactoryInitialization方法


在Spring初始化流程中,执行AbstractApplicationContext#finishBeanFactoryInitialization方法。 该方法里面发生的主要流程为Spring业务Bean初始化。 实际流程跟Spring Bean的初始化没有任务区别。



InstantiationAwareBeanPostProcessor


  • 通过对接口 InstantiationAwareBeanPostProcessor 实现类的方法进行执行。 仅此而已。


AutowiredAnnotationBeanPostProcessor
  • InjectionMetadataInjectionMetadataInjectedElementInjectedElement这个类是 InstantiationAwareBeanPostProcessor的一个实现类。



@Value和@Autowired注解实际执行


  1. 用于@Value和@Autowired注解实际执行方法postProcessPropertyValues调度实际调度InjectedElement子类被注入值的获取来自于DefaultListableBeanFactory将对应@Value(“${configValue}”)里面的值替换的来源值,是PropertySourcesPlaceholderConfigurer生成的StringValueResolver。
  2. Spring原生的Bean是单例的它直接被储存在了AbstractBeanFactory执行Field.set(Object, Object)或者Method.invoke(Object, Object[])。



所以,可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer仅仅是做了一个配置文件的解析工作,真正的注入并不由它们完成,而是托付给了Spring 的Bean初始化流程。这两个类实现了BeanFactoryPostProcessor 接口,这个接口的优先级高于后续的Spring Bean。



通过解析了的PropertySourcesPlaceholderConfigurer查询得到元素值。 没有则抛出异常,如下源码:


DefaultListableBeanFactory#doResolveDependency



@Value 注解值进行属性占位符解析和替换

// 获取注解的 value() 值。被写死为 Class<? extends Annotation> valueAnnotationType = Value.class;
// 见类 QualifierAnnotationAutowireCandidateResolver
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
    if (value instanceof String) {
        // 通过PropertySourcesPlaceholderConfigurer写入的键值对元素获取元素的值.
        // 方法内注册了多个StringValueResolver,循环查找值。提供者为PropertySourcesPlaceholderConfigurer,因此配置多个解析器的时候是以最后的配置为准的。
        String strVal = resolveEmbeddedValue((String) value);
        BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
        value = evaluateBeanDefinitionString(strVal, bd);
    }
    TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
    return (descriptor.getField() != null ?
            converter.convertIfNecessary(value, type, descriptor.getField()) :
            converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
复制代码

读取配置的方式介绍


xml文件读取配置信息案例


通过PropertyPlaceholderConfigurer进行配置Bean方式


单个配置文件。

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
     <value>conf/sqlmap/jdbc.properties</value>
   </property>
    <property name="fileEncoding">
      <value>UTF-8</value>
    </property>
</bean>
复制代码

多个配置文件


注意这两种value值的写法

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
    <property name="locations">
        <list>
            <value>/WEB-INF/mail.properties</value>  
            <value>classpath: conf/sqlmap/jdbc.properties</value>
     </list>
    </property>
</bean>
复制代码



Spring标签方式

<context:property-placeholder location="classpath*:/WEB-INF/mail.properties" />
复制代码

这总方式的原理就是构造一个PropertySourcesPlaceholderConfigurer, (3.1之前是


PropertyPlaceholderConfigurer)


  • ContextNamespaceHandler#init
  • PropertyPlaceholderBeanDefinitionParser#doParse



注入配置触发点


Spring初始化Context的时候读取XML配置, 这个流程优先于Spring普通Bean初始化。配合扫包(<context:component-scan />)得到的Bean进而实现对XML里面配置的Bean的载入。


  • PropertySourcesPlaceholderConfigurer本质上是一个BeanFactoryPostProcessor。解析XML的流程在BeanFactoryPostProcessor之前, 优先将配置文件的路径以及名字通过Setter传入


  • PropertySourcesPlaceholderConfigurer。



总结Spring Value注入流程


构建PropertySourcesPlaceholderConfigurerBean或者PropertyPlaceholderConfigurerBean的组件


  1. 配置Spring @Value("val2Inject") 方式获取配置文件的属性,需要依赖于在Spring XML里面配置<context:property-placeholder /> 或者PropertySourcesPlaceholderConfigurerBean来添加配置文件的名称。


  1. 读取到context:property-placeholder标签或者PropertySourcesPlaceholderConfigurer 解析并实例化一个PropertySourcesPlaceholderConfigurer。同时向其中注入配置文件路径、名称PropertySourcesPlaceholderConfigurer自身生成多个StringValueResolver备用,Bean准备完毕。


  1. Spring在初始化非BeanFactoryPostProcessor的Bean的时候,AutowiredAnnotationBeanPostProcessor负责找到Bean内有@Value注解的Field或者Method
  • 通过PropertySourcesPlaceholderConfigurer寻找合适的StringValueResolver并解析得到val值。注入给@Value的Field或Method。


  1. AutowiredAnnotationBeanPostProcessor负责@Autowired和@Value两个注解的解析。





@PropertySource注解配置读取单个或多个配置文件


单个配置文件:

@PropertySource(value = "classpath:config/application-config.properties")
复制代码



多个配置文件:

@PropertySource(value = {"classpath:config/application-config1.properties","classpath:config/application-config2.properties"})
复制代码



@PropertySource注解使用有两种方式


  1. @PropertySource + Environment,通过@PropertySource注解将properties配置文件中的值存储到Spring的Environment中,Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。


  1. @PropertySource(PropertySourcesPlaceholderConfigurer) +@Value


@PropertySource + Environment

@Configuration
@ComponentScan(basePackages = "com.libo.config")
@PropertySource(value = "classpath:config/application-config.properties")
public class TestPropertieEnvironment { 
    @Autowired
    Environment environment;
    public String properties(){
        String key = this.environment.getProperty("config.key");
        System.out.println(key);
        return null;
    }
}
复制代码



配置文件config.properties:
config.key=1
config.value=2
复制代码



测试类操作
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TestPropertieEnvironment.class);
        ServiceConfiguration hc2 = (TestPropertieEnvironment) context.getBean("testPropertieEnvironment");
        hc2.properties();
    }
}
复制代码



@PropertySource(PropertySourcesPlaceholderConfigurer)+@Value


PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化实现。它用于解析bean定义中的属性值,以及注解@Value的值,使用的属性来源是当前的Spring Environment对象,以及设置给自己的PropertySources对象。


  • 大于3.1更高版本中,缺省使用该工具替换了PlaceholderConfigurerSupport
  • <=3.0较老的Spring中,为了保持和之前的版本兼容,缺省还是使用PropertyPlaceholderConfigurer。



创建PropertySourcesPlaceholderConfigurer


创建PropertiesConfig
@Component
@PropertySource(value = "classpath:config/application-config.properties")
public class PropertiesConfig {
    @Value("${config.value}")
    private String value;
    @Value("${config.key}")
    private String key;
}
复制代码

测试类忽略!





自定义PropertyPlaceholderConfigurer


@Configuration
@ComponentScan(basePackages = "com.libo.config")
public class PropertiesConfiguration2 { 
    @Bean 
    public static PropertyPlaceholderConfigurer configurer() { 
         PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
         Resource resources = new ClassPathResource( "config/appplication-config.properties" );
         ppc.setLocation(resources);
         return ppc; 
    } 
    @Bean
    public Configs2 configs2(@Value("${ds.user}") String user, @Value("${key1}") String key1) {
        Configs2 configs = new Configs2();
        configs.setApiKeyId(user);
        configs.setSecretApiKey(key1);
        System.out.println("in ServiceConfiguration" + configs);
        return configs;
    }
}
@Service
public class TestConfigs2 {
    @Autowired
    Configs2 configs2;
    @Autowired
    Configs configs;
    public void testConfigs2() {
        System.out.println("configs:"+configs.getApiKeyId());
        System.out.println("configs2:"+configs2.getApiKeyId());
    }
}
复制代码



测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ServiceConfiguration2.class);       
        TestConfigs2 hc2 = (TestConfigs2) context.getBean("testConfigs2");
        hc2.testConfigs2();
    }
}
复制代码

此外需要注意的是:PropertySource是可以支持ignoreResourceNotFound支持无法获取配置文件的i情况。




Spring4版本的PropertySources的注解


在Spring 4版本中,Spring提供了一个新的注解——@PropertySources,从名字就可以猜测到它是为多配置文件而准备的。

@PropertySources({
//@PropertySource("classpath:db.properties"),
@PropertySource(value="classpath:db.properties", ignoreResourceNotFound=true),
@PropertySource("classpath:spring/config.properties")
  public class AppConfig {
    @Value("${key1}")
    private String key1;
    @Value("${key2}")
    private String key2;
    @Override
    public String toString() {
        return "AppConfig [key1=" + key1 + ", key2=" + key2 + "]";
    } 
}




相关文章
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
24天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
8天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
19 0
|
14天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
40 9
|
29天前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
109 6
|
1月前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
74 2
|
6月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
95 1
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
113 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
128 9
下一篇
无影云桌面