深入浅出spring原理及实战「源码原理实战」从底层角度去分析研究propertysourcesplaceholderconfigurer的原理及实战注入机制

author author     2022-12-19     486

关键词:

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的特殊化实现。

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

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

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来添加配置文件的名称。
  2. 读取到context:property-placeholder标签或者PropertySourcesPlaceholderConfigurer
    解析并实例化一个PropertySourcesPlaceholderConfigurer。同时向其中注入配置文件路径、名称PropertySourcesPlaceholderConfigurer自身生成多个StringValueResolver备用,Bean准备完毕。
  3. 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值。
  2. @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 + "]";

深入浅出spring原理及实战「源码调试分析」结合datasourceregister深入分析importbeandefinitionregistrar的源码运作流程(代码片段)

每日一句人的一生中不可能会一帆风顺,总会遇到一些挫折,当你对生活失去了信心的时候,仔细的看一看、好好回想一下你所遇到的最美好的事情吧,那会让你感觉到生活的美好。注入案例代码如何通过实现Spri... 查看详情

深入浅出spring原理及实战「原理分析专题」不看源码就带你剖析ioc容器核心流程以及运作原理

这是史上最全面的Spring的核心流程以及运作原理的分析指南🍃【Spring核心专题】「IOC容器篇」不看繁琐的源码就带你浏览Spring的核心流程以及运作原理🍃【Spring核心专题】「AOP容器篇」不看繁琐的源码就带你浏览Spring的... 查看详情

深入浅出spring原理及实战「开发实战系列」spring-cache扩展自定义(注解失效时间+主动刷新缓存)(代码片段)

缓存失效时间支持在方法的注解上指定SpringCache默认是不支持在@Cacheable上添加过期时间的,可以在配置缓存容器时统一指定:@BeanpublicCacheManagercacheManager(@SuppressWarnings("rawtypes")RedisTemplateredisTemplate)Custo 查看详情

深入浅出spring原理及实战「原理分析专题」不看源码就带你剖析mvc容器核心流程以及运作原理

前提回顾之前已经写了很多问斩针对于SpringMVC的的执行原理和核心流程,在此再进行冗余介绍就没有任何意义了,所以我们主要考虑的就是针对于SpringMVC还没但大框架有介绍的相关内容解析分析和说明,那么接下来就... 查看详情

阅读书单2020

...精通MongoDB实战(第二版)应用密码学协议、算法与C源程序深入浅出密码学——常用加密技术原理与应用机器学习深入理解Java虚拟机:JVM高级特性与最佳实践HotSpot实战深入浅出promethuseElasticSearch源码解析与优化实践Tensorflow:... 查看详情

深入浅出spring原理及实战「原理分析专题」不看源码就带你剖析aop容器核心流程以及运作原理(代码片段)

前提回顾前一篇文章主要介绍了spring核心特性机制的IOC容器机制和核心运作原理,接下来我们去介绍另外一个较为核心的功能,那就是AOP容器机制,主要负责承接前一篇代理模式机制中动态代理:JDKProxy和CglibProxy... 查看详情

深入浅出spring原理及实战「开发实战系列」重新回顾一下异常重试框架springretry的功能指南和实战(代码片段)

重试机制的业务背景外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要用失败重试策略重新调用API接口来获取。在分布式系统中... 查看详情

深入浅出spring原理及实战「原理分析专题」从零开始教你springel表达式使用和功能分析讲解指南(上篇)(代码片段)

SpringEL表达式语言,这种语言jsp中学到的el,但是在整个spring之中其表达式语言要更加的复杂,而且支持度更加的广泛,最重要的是他可以进行方法的调用,对象的实例化,集合操作等等,但是唯一的难点就是:代码太复杂了,表达式太复杂... 查看详情

精华推荐|深入浅出rocketmq原理及实战「底层源码挖掘系列」透彻剖析贯穿rocketmq的消费者端的运行核心的流程(上篇)

精华推荐|【深入浅出RocketMQ原理及实战】「底层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行核心的流程上篇:分析对应总体消费流程的判断和校验以及限流控制和回调等处理流程分析下篇:分析基于上篇的总体流程的... 查看详情

深入浅出spring原理及实战「开发实战系列」springsecurity技术实战之通过注解表达式控制方法权限(代码片段)

SpringSecurity权限控制机制SpringSecurity中可以通过表达式控制方法权限,其中有四个支持使用表达式的注解,分别是@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。其中前两者可以用来在方法调用前或者调用后进行... 查看详情

深入浅出spring原理及实战「开发实战系列」手把手教你将@schedule任务调度升级为分布式调度@distributeschedule(代码片段)

背景介绍很多小伙伴们都跟我留言说过一个类似的问题,就是针对于任务调度框架而言的选取,很多公司都会采用任务调度框架的鼻祖Quartz,那么我们来梳理以下Java领域的任务调度框架吧。Java领域的定时任务的框架... 查看详情

hibernate实战源码解析hibernate参数绑定及preparedstatement防sql注入原理(代码片段)

...非一篇文章或一篇专题就能说得完。本文从使用入手在【Spring实战】----Spring4.3.2集成Hibernate5.2.5基础上继续深入研究。本文包含以下内容:SQL语 查看详情

深入浅出springcloud原理及实战「netflix系列之fegin」从源码层面让你认识feign工作流程和运作机制(代码片段)

Feign工作流程源码解析什么是feign:一款基于注解和动态代理的声明式restfulhttp客户端。原理Feign发送请求实现原理微服务启动类上标记@EnableFeignClients注解,然后Feign接口上标记@FeignClient注解。@FeignClient注解有几个... 查看详情

深入浅出sentinel原理及实战「基础实战专题」零基础实现服务流量控制实战开发指南

你若要喜爱你自己的价值,你就得给世界创造价值。Sentinel的组成部分Sentinel主要由以下两个部分组成。Sentinel核心库(Java客户端):Sentinel的核心库不依赖任何框架或库,能够运行于Java8及以上的版本的运行时环境中,同时对Spri... 查看详情

深入浅出spring原理及实战「开发实战系列」采用protostuff和kryo高性能序列化框架实现redistemplate的序列化组件(代码片段)

序列化序列化可以简单理解为对象–>字节的过程,同理,反序列化则是相反的过程。为什么需要序列化?因为网络传输只认字节。所以互信的过程依赖于序列化。网络传输的性能等诸多因素,通常会支持多种序... 查看详情

2021网易java高级面试题及答案,原理+实战+视频+源码

Spring框架自诞生以来一直备受开发者青睐,有人亲切的称之为:Spring全家桶。Spring更是避免了重复造轮子的工作并跟随着互联网行业的发展做出不断的更新,很多研发人员把spring看作心目中最好的Java项目,没有之... 查看详情

深入浅出dubbo3原理及实战「技术大纲」深入浅出并发实战课程系列及技术指南

Dubbo3开题简介如开篇所述,Dubbo提供了构建云原生微服务业务的一站式解决方案,可以使用Dubbo快速定义并发布微服务组件,同时基于Dubbo开箱即用的丰富特性及超强的扩展能力,构建运维整个微服务体系所需的各... 查看详情

深入浅出sentinel原理及实战「基础实战专题」零基础探索分析sentinel控制台开发指南

Sentinel控制台Sentinel提供了一个轻量级的开源控制台SentinelDashboard,它提供了机器发现与健康情况管理、监控(单机和集群)、规则管理与推送等多种功能。Sentinel控制台提供的功能如下查看机器列表以及健康情况:Sentnel控制台能... 查看详情