2019独角兽企业重金招聘Python工程师标准>>>

简介

最近工作中需要使用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/beanshttp://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的定义,注册BeanDefinitionConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//为BeanFactory配置容器特性,例如类加载器、事件处理器等  prepareBeanFactory(beanFactory);try // 内容为空的方法,留给子类按需覆写postProcessBeanFactory(beanFactory);//在这里调用每个BeanFactoryPostProcessor实现类的postProcessBeanFactoryinvokeBeanFactoryPostProcessors(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。注意:是定义不是初始化后的实例。

invokeBeanFactoryPostProcessors()方法,将会对所有实现BeanFactoryPostProcessor接口的bean初始化,并调用postProcessBeanFactory方法。下面一起看下 invokeBeanFactoryPostProcessors 方法。

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 nameString[] 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类处理。

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

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

转载于:https://my.oschina.net/thinwonton/blog/1104546

spring源码分析,聊聊PropertyPlaceholderConfigurer相关推荐

  1. Spring源码分析八:Mybatis ORM映射框架原理

    文章目录 (一)Mybatis单独操作数据库程序 1.1.数据库表 1.2.建立PO 1.3.建立mapper接口映射 1.4.建立Mybatis配置文件 1.5.建立mapper映射文件 1.6.测 ...

  2. Spring源码分析【1】-Tomcat的初始化

    org.apache.catalina.startup.ContextConfig.configureStart() org.apache.catalina.startup.ContextConfig ...

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

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

  4. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  5. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  6. spring源码分析第六天------spring经典面试问题

    spring源码分析第六天------spring经典面试问题 1.Spring5 新特性及应用举例 2.Spring 经典的面试问题 a.什么是 Spring 框架?Spring 框架有哪些主要模块 ...

  7. spring源码分析第五天------springAOP核心原理及源码分析

    spring源码分析第五天------springAOP核心原理及源码分析 1. 面向切面编程.可以通过预 编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术 切面(A ...

  8. spring源码分析第四天------springmvc核心原理及源码分析

    spring源码分析第四天------springmvc核心原理及源码分析 1.基础知识普及 2. SpringMVC请求流程 3.SpringMVC代码流程 4.springMVC源码分析 4.1 ...

  9. spring源码分析第一天------源码分析知识储备

    spring源码分析第一天------源码分析知识储备 Spring源码分析怎么学? 1.环境准备: 2.思路    看:是什么? 能干啥    想:为什么?     实践:怎么做?         ...

  10. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

最新文章

  1. 算法---字符串去重
  2. python安卓打包_Android多渠道打包之Python打包
  3. Java敌人跟地图的碰撞_(译)加入敌人和战斗:如果使用cocos2d制作基于tiled地图的游戏:第三部分...
  4. oracle获取分组后的统计信息,并只要前五条
  5. Walle 瓦力 web部署系统
  6. 天池 在线编程 输入流
  7. 3行代码实现ftp 上传、下载、删除操作集合
  8. 花呗下调部分年轻用户额度,倡导理性消费,网友:是嫌我穷了吗?
  9. 【Windows 8 Store App】学习三:HTTP
  10. 3dsmax软件和maya软件各自的优劣势是什么?
  11. ​​​​浏览器中添加Json解析工具
  12. 【java毕业设计】 基于java+SSH+JSP的保险业务管理系统设计与实现(毕业论文+程序源码)——保险业务管理系统
  13. 数据挖掘案例(1):泰坦尼克号-数据挖掘流程
  14. Pytorch:目标检测网络-FPN
  15. 国际C语言混乱代码大赛(IOCCC)1988年获奖作品
  16. 瑞金医院启动神经领域 AI 技术临床研究;好未来、清华大学成立研究中心,共建“AI+教育”智慧课堂...
  17. android11更新了什么,一加8安卓11更新了什么 一加8安卓11更新内容
  18. 小白日记29:kali渗透测试之Web渗透-扫描工具-Vega
  19. 广州推出全国首个交通运输视频智能化综合分析平台
  20. Codeforces 91A-Newspaper Headline

热门文章

  1. 数据库------事务
  2. 百词斩和扇贝打卡测试与评估
  3. 阿里前CEO卫哲的万字长文:被马云骂醒,看透B2B 10大核心问题!
  4. WDCP管理面板安装启动EXIF、bcmath完整步骤
  5. UI进阶--Quartz2D和触摸事件的简单使用:简易涂鸦板
  6. SHD0新建屏幕变式
  7. OpenCV3.2+VS2013+Tesseract3.02.02配置
  8. make run_py时ImportError: No module named cv2
  9. 安装hadoop伪分布式集群心得
  10. 科技人员在计算机前的肖像,首个全国科技工作者日来了 西南大学学子为科技工作者画像...