spring源码分析,聊聊PropertyPlaceholderConfigurer

Easter79
• 阅读 682

简介

最近工作中需要使用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。注意:是定义不是初始化后的实例。

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 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占位符。

看下它的类图:

spring源码分析,聊聊PropertyPlaceholderConfigurer

  • 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

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k