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实现读取配置的。
  • PropertySourcesPlaceholderConfigurer是PlaceholderConfigurerSupport的特殊化实现。

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

PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer的执行目标

PropertyPlaceholderConfigurer和PropertyPlaceholderConfigurer在使用上并无本质的区别,两者的根本目标是将配置文件生成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。
  4. 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原理及实战】「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机制的更多相关文章

  1. 【Java实战】源码解析Java SPI(Service Provider Interface )机制原理

    一.背景知识 在阅读开源框架源码时,发现许多框架都支持SPI(Service Provider Interface ),前面有篇文章JDBC对Driver的加载时应用了SPI,参考[Hibernate ...

  2. 阿里P7终于讲完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源码

    前言 这里普及一下,每个公司都有职别定级系统,阿里也是,技术岗以 P 定级,一般校招 P5, 社招 P6 起.其实阅读源码也是有很多诀窍的,这里分享几点心得: 首先要会用.你要知道这个库是干什么的,掌 ...

  3. Apache Beam WordCount编程实战及源码解读

    概述:Apache Beam WordCount编程实战及源码解读,并通过intellij IDEA和terminal两种方式调试运行WordCount程序,Apache Beam对大数据的批处理和流 ...

  4. Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析

    Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...

  5. 七、Spring之深入理解AOP源码

    Spring之深入理解AOP源码 ​ 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. ​ 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...

  6. SpringCloudGateway微服务网关实战与源码分析 - 中

    实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...

  7. SpringCloudAlibaba分布式事务解决方案Seata实战与源码分析-上

    概述 定义 Spring Cloud Alibaba Seata 官网地址 https://seata.io/zh-cn/ 最新版本1.5.2 Spring Cloud Alibaba Seata 文 ...

  8. 各个版本spring的jar包以及源码下载地址

    各个版本spring的jar包以及源码下载地址,目前最高版本到spring4.1.2,留存备用: http://maven.springframework.org/release/org/spring ...

  9. Spring Boot 2.0系列文章(五):Spring Boot 2.0 项目源码结构预览

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/04/15/springboot2_code/ 项目结构 结构分析: Spring-boot-pr ...

  10. Spring Environment(二)源码分析

    Spring Environment(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Envi ...

随机推荐

  1. Node.js(一)基本node.js读取删除

    npm init -y(初始化项目) npm uninstall (xxx模块名称)=>(移除模块) var text2=require("./text2"); //解构 c ...

  2. VideoPipe可视化视频结构化框架开源了!

    完成多路视频并行接入.解码.多级推理.结构化数据分析.上报.编码推流等过程,插件式/pipe式编程风格,功能上类似英伟达的deepstream和华为的mxvision,但底层核心不依赖复杂难懂的gst ...

  3. VMware vSphere 8.0 正式版下载

    请访问原文链接:https://sysin.org/blog/vmware-vsphere-8/,查看最新版.原创作品,转载请保留出处. 作者主页:www.sysin.org vSphere 8.0 ...

  4. Containerd-1.6.5 镜像容器操作

    一.Containerd 镜像操作 1 基本参数 [root@ecs-65685 ~]# ctr c NAME: ctr containers - manage containers USAGE: c ...

  5. 基于win11安装Java11环境

    下载JDK解压版本 下载后解压,并放到一个没有中文路径的目录,如图所示: 配置Java环境变量 以Windows11系统为例,打开设置,搜索"环境",点击"编辑系统环境变 ...

  6. 2022年最新最详细IDEA关联数据库方式、在IDEA中进行数据库的可视化操作(包含图解过程)

    文章目录 1.使用IDEA关联Mysql数据库的详细操作步骤 1.1 打开侧边栏的Database 2.2. 选择要连接的数据库(Mysql) 2.3 .输入要连接的数据库.用户名.密码 2.4 .点 ...

  7. 七、docker镜像私有仓库

    在Docker中,当我们执行 docker pull xxx 的时候 ,它实际上是从 hub.docker.com 这个地址去查找,这就是 Docker 公司为我们提供的公共仓库.在工作中,我们不可能 ...

  8. Python基础之面向对象:3、继承与派生

    面向对象 一.三大特征之继承 python三大特征: 封装.继承.多态 三者中继承最为核心,实际应用对,感受较为直观 封装和多态略微抽象 1.继承的概念 继承的含义: ​ 在现实生活中,继承表示人与人 ...

  9. VP记录

    预计在最后的日子里适量VP 简单记录一下 CF 1037 Link 上来秒了ABCD,很快啊 A是二进制拆分,B是一眼贪心,C是一个非常简单且好写的dp D把边遍历顺序按照所需的bfs顺序排序,最后比 ...

  10. 从 洛谷P5309 Ynoi2011 初始化 看卡常

    一般情况下,程序运行消耗时间主要与时间复杂度有关,超时与否取决于算法是否正确. 但对于某些题目,时间复杂度正确的程序也无法通过,这时我们就需要卡常数,即通过优化一些操作的常数因子减少时间消耗. 比如这 ...