简介

最近工作中需要使用zookeeper配置中心管理各系统的配置,也就是需要在项目启动时,加载zookeeper中节点的子节点的数据(例如数据库的地址,/config/db.properties/db.addr),并替代spring xml里的占位符。既然需要替代占位符,那么自然会想到PropertyPlaceholderConfigurer这个类,该类实现了在容器的bean初始化前,替代spring容器的BeanDefinition中的值。

本文将对PropertyPlaceholderConfigurer源码进行解析。

为了简化整个分析流程,假设定义了一个bean ZookeeperUtil,需要PropertyPlaceholderConfigurer类修改beanDefinition定义,替换${zookeeper.addr}。

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations">
<array>
<!--不同容器之间的属性不能相互访问-->
<value>classpath:config.properties</value>
</array>
</property>
</bean> <bean class="com.github.thinwonton.spring.source.analysis.ZookeeperUtil">
<property name="addr" value="${zookeeper.addr}"/>
</bean>
</beans>

什么是BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanFactoryPostProcessor接口是spring的一个扩展点。它提供了在容器创建bean之前,对bean的定义(配置元数据)进行处理的方法。

BeanFactoryPostProcessor接口定义了一个抽象方法:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

其中,beanFactory是bean工厂,里面封装了beanDefinition,也就是bean在xml的定义。

postProcessBeanFactory 什么时候调用呢?

在spring容器初始化时,AbstractApplicationContext.class的refresh()方法会被调用,该refresh()方法如下

 public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 记录启动时间,设置启动标识
prepareRefresh(); // 创建beanFactory;解析spring配置文件;获取bean的定义,注册BeanDefinition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory); try
// 内容为空的方法,留给子类按需覆写
postProcessBeanFactory(beanFactory); //在这里调用每个BeanFactoryPostProcessor实现类的postProcessBeanFactory
invokeBeanFactoryPostProcessors(beanFactory); //为BeanFactory注册BeanPost事件处理器.
registerBeanPostProcessors(beanFactory); //初始化信息源,和国际化相关.
initMessageSource(); //初始化容器事件传播器.
initApplicationEventMulticaster(); //调用子类的某些特殊Bean初始化方法
onRefresh(); //为事件传播器注册事件监听器.
registerListeners(); //初始化所有剩余的单例Bean.
finishBeanFactoryInitialization(beanFactory); //初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
} catch (BeansException ex) {
//销毁以创建的单态Bean
destroyBeans();
//取消refresh操作,重置容器的同步标识.
cancelRefresh(ex);
throw ex;
}
}
}

上面的流程中,在invokeBeanFactoryPostProcessors()被调用之前,spring容器创建了beanFactory,并在beanFactory中保存了spring配置文件中bean的定义。该定义包括了前面xml中定义的两个bean,一个是PropertyPlaceholderConfigurer,另一个是ZookeeperUtil。注意:是定义不是初始化后的实例。

 protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<String>(); // 忽略代码,不影响下面分析 // 根据类型,从bean factory中获取bean名称的列表。bean names在创建bean factory这个容器的时候,已经从xml中读取并缓存了。
// 在这里是需要获取BeanFactoryPostProcessor.class类型的bean name
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); // 筛选出哪些BeanFactoryPostProcessor的实现类实现了PriorityOrdered、Ordered接口,并把它们的bean name放到相应的列表中
// PriorityOrdered、Ordered以及在xml中声明的顺序,影响BeanFactoryPostProcessor的实现类被调用的顺序
// PropertyPlaceholderConfigurer的父类PropertyResourceConfigurer实现了PriorityOrdered接口,所以它会被加入到priorityOrderedPostProcessors列表中 List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
List<String> orderedPostProcessorNames = new ArrayList<String>();
List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (isTypeMatch(ppName, PriorityOrdered.class)) {
//这个分支非常奇怪,居然在这里就实例化,然后把实例化的实现类放到集合中,并与下面的分支处理方式不同,肯定不是同一个程序员写的
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
} // 首先,调用实现了优先级接口的BeanFactoryPostProcessor实现类,同样实现优先级接口的类通过getOrder()的返回值进行排序,决定调用顺序
OrderComparator.sort(priorityOrderedPostProcessors);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); // 然后,调用实现了Ordered接口的BeanFactoryPostProcessor实现类,同样实现Ordered接口的类通过getOrder()的返回值进行排序,决定调用顺序
List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
OrderComparator.sort(orderedPostProcessors);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); // 最后,调用普通的的BeanFactoryPostProcessor实现类,它的顺序由配置文件XML的声明顺序决定
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
}

上面的注释详细解析了 invokeBeanFactoryPostProcessors 方法的流程,该方法将从bean factory中获取所有实现BeanFactoryPostProcessor接口的bean名称,并对bean name列表进行了排序,最后根据bean name实例化它们,并调用BeanFactoryPostProcessor接口的postProcessBeanFactory方法。

在这里,实例化BeanFactoryPostProcessor是通过 beanFactory的getBean() 方法实现的,该方法非常复杂,不是本文的讨论范畴。


PropertyPlaceholderConfigurer源码解析

PropertyPlaceholderConfigurer,用于将properties文件中定义的属性替换到bean定义的property占位符。

看下它的类图:

  • PropertiesLoaderSupport:属性加载帮助类,提供从properties文件中读取配置信息的能力,该类的属性locations指定需要加载的文件所在的路径。

  • PropertyResourceConfigurer:属性资源的配置类,实现了BeanFactoryPostProcessor接口,因此,在容器初始化的时候,调用的就是该类的实现方法 postProcessBeanFactory() 。在 postProcessBeanFactory()方法中,从配置文件中读取了配置项,最后调用了它的抽象方法 processProperties(),由子类决定怎么处理这些配置属性。除此之外,提供了convertProperty()方法,该方法是个扩展点,其实里面什么都没做,它可以用来子类在处理这些配置信息前,对配置信息进行一些转换,例如配置属性的解密。

  • PropertyPlaceholderConfigurer:该类实现了父类PropertyResourceConfigurer的抽象方法processProperties()。processProperties()方法会创建PlaceholderResolvingStringValueResolver类,该类提供解析字符串的方法resolveStringValue。创建了StringValueResolver实现类后,交由它的父类PlaceholderConfigurerSupport的doProcessProperties()处理。另外,占位符的值替换为properties中的值的实际处理类。

  • PlaceholderConfigurerSupport:该类持有占位符符号的前缀、后缀,并在doProcessProperties()模板方法中,对BeanDefinition实例中的占位符进行替换。

  • BeanDefinition:在spring容器初始化时,扫描并获取每个bean的声明(例如在xml中声明、通过注解声明等),然后组装成BeanDefinition,它描述了一个bean实例,拥有属性值,构造参数值和具体实现提供的其他信息。

  • BeanDefinitionVisitor:负责访问BeanDefinition,包括(1)从beanDefinition实例中,获取spring约定的可以替换的参数;(2)使用占位符解析器解析占位符,并从properties中获取它对应的值,最后把值设置到BeanDefinition中。

  • PropertyPlaceholderHelper:持有占位符的前缀、后缀、多值的分隔符,负责把占位符的字符串去除前缀、后缀,对于字符串的替换,委托给PropertyPlaceholderConfigurerResolver类处理。

  • PropertyPlaceholderConfigurerResolver:该类委托给PropertyPlaceholderConfigurer类处理。

接下来我们看一下时序图,帮助理解上述的类图和整个解析占位符的过程。 

时序图,配合上面的类图看,效果更佳!

spring源码分析:PropertyPlaceholderConfigurer的更多相关文章

  1. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

  2. Spring源码分析-BeanFactoryPostProcessors 应用之 PropertyPlaceholderConfigurer

    BeanFactoryPostProcessors 介绍 BeanFactoryPostProcessors完整定义: /** * Allows for custom modification of ...

  3. spring源码分析系列

    spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor spring源码分析系列 ...

  4. 框架-spring源码分析(一)

    框架-spring源码分析(一) 参考: https://www.cnblogs.com/heavenyes/p/3933642.html http://www.cnblogs.com/BINGJJF ...

  5. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  6. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  7. Spring源码分析——BeanFactory体系之抽象类、类分析(一)

    上一篇介绍了BeanFactory体系的所有接口——Spring源码分析——BeanFactory体系之接口详细分析,本篇就接着介绍BeanFactory体系的抽象类和接口. 一.BeanFactor ...

  8. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  9. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  10. spring源码分析

    编译问题 spring-4.0.5.release编译是用jdk8编译的,为啥可以运行在jdk7的环境? 源码分析 spring源码分析,由一个点各个击破,比如依赖注入,autowired. spri ...

随机推荐

  1. windows下oracle数据库报错ORA-12705解决方法

    转自:http://blog.sina.com.cn/s/blog_16eaf6b940102x66q.html 有个朋友,他们那边windows虚拟机重启后,数据库不能起来报错ORA-12705无法 ...

  2. 转载 AI-Talking 图算法

    https://mp.weixin.qq.com/s/2XRgJr-ydxHA3JxAZ_5HeA 图算法在风控业务的实践 直播行业中有很多业务风控问题,比如说批量注册.刷热度.垃圾信息以及薅羊毛等. ...

  3. 2019年6月车型数据Access数据库+缩略图 更新于2019年6月5日.

    工作需要才来采集的, 数据来源某卡汽车网, 分享出来给需要的人吧, 本着分享的精神, 我就不猥琐的放到csdn下载了 本来是sql server的, 我导出到access了, 也方便大家查看. 顺手抓 ...

  4. cmake简单用法

    CMake是一个跨平台的编译工具,类似于automake 安装 # cd cmake-2.8.10.2 # ./bootstrap # make # make install project proj ...

  5. Android笔记(十九) Android中的Fragment

    通常我们使用Activity来展示界面,但是在手机上界面可能显示的很好看,但在平板上,因为平板的屏幕非常大,手机的界面放在平板上可能会出现控件被拉长.控件之间间距变大等问题.为了更好的体验效果,在Ac ...

  6. kali linux Desktop Environemt types and kali linux install virualbox

    1.we know the kali linux desktop environmet can also be costomized ,Desktop environmet can use GNOME ...

  7. 商汤开源的mmdetection技术报告

    目录 1. 简介 2. 支持的算法 3. 框架与架构 6. 相关链接 前言:让我惊艳的几个库: ultralytics的yolov3,在一众yolov3的pytorch版本实现算法中脱颖而出,收到开发 ...

  8. Python 写入训练日志文件并控制台输出

    1. 背景 在深度学习的任务中,通常需要比较长时间的训练,因此我们会选择离开电脑.笔者在跟踪模型表现, 观察模型accuracy 以及 loss 的时候,比较传统的方法是在控制台print输出或者直接 ...

  9. 用 Redis 实现延时任务

    原文:https://cloud.tencent.com/developer/article/1358266 1.什么是延时任务 延时任务,顾名思义,就是延迟一段时间后才执行的任务.延时任务的使用还是 ...

  10. SegNet网络的Pytorch实现

    1.文章原文地址 SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation 2.文章摘要 语义分 ...