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

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

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。
  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. day42-反射01

    Java反射01 1.反射(reflection)机制 1.1反射机制问题 一个需求引出反射 请看下面问题: 根据配置文件 re.properties 指定信息,创建Cat对象并调用方法hi clas ...

  2. POJ3342 Party at Hali-Bula(树形DP)

    dp[u][0]表示不选u时在以u为根的子树中最大人数,dp[u][1]则是选了u后的最大人数: f[u][0]表示不选u时的唯一性,f[u][1]是选了u后的唯一性,值为1代表唯一,0代表不唯一. ...

  3. 插件化编程之WebAPI统一返回模型

    WebApi返回数据我们一般包裹在一个公共的模型下面的,而不是直接返回最终数据,在返回参数中,显示出当前请求的时间戳,是否请求成功,如果错误那么错误的消息是什么,状态码(根据业务定义的值)等等.我们常 ...

  4. 微服务开发框架-----Apache Dubbo

    文章目录 一.简介 二.概念与架构 一.简介 Apache Dubbo 是一款微服务开发框架,提供了RPC通信与微服务治理两大关键能力.使用Dubbo开发的微服务,将具备相互之间的远程发现与通信能力, ...

  5. 驱动开发:内核测试模式过DSE签名

    微软在x64系统中推出了DSE保护机制,DSE全称(Driver Signature Enforcement),该保护机制的核心就是任何驱动程序或者是第三方驱动如果想要在正常模式下被加载则必须要经过微 ...

  6. 知识图谱-生物信息学-医学顶刊论文(Bioinformatics-2021)-MUFFIN:用于DDI预测的多尺度特征融合

    2.(2021.3.15)Bioinformatics-MUFFIN:用于DDI预测的多尺度特征融合 论文标题: MUFFIN: multi-scale feature fusion for drug ...

  7. element-plus 消息提示

    用来显示「成功.警告.消息.错误」类的操作 <template> <el-button :plain="true" @click="open2" ...

  8. 真正“搞”懂HTTP协议03之时间穿梭

    上一篇我们简单的介绍了一下DoD模型和OSI模型,还着重的讲解了TCP的三次握手和四次挥手,让我们在空间层面,稍稍宏观的了解了HTTP所依赖的底层模型,那么这一篇,我们来追溯一下HTTP的历史,看一看 ...

  9. zk系列二:zookeeper实战之分布式统一配置获取

    前面介绍了zk的一些基础知识,这篇文章主要介绍下如何在java环境下获取zk的配置信息:主要基于zk的监听器以及回调函数通过响应式编程的思想将核心代码糅合成一个工具类,几乎做到了拿来即用: 在分布式集 ...

  10. docker清空网络配置

    docker 网络 故障 相同的 ip 绑定给了 两个 网卡, 需要 清空 网络 ip addr | grep 10.79 inet 10.79.106.1/24 brd 10.79.106.255 ...