深度剖析Spring FactoryBean的初始化以及Bean的获取过程(上)

前言

剖析SpringIOC容器初始化源码的文章有很多,但大多都是直接上代码注释或直接根据UML图逐行解读源码。我认为即使如此,阅读源码的工作依然是否艰巨。因为Spring作为一个风靡全球的框架,其代码里面存在大量的bug修复痕迹,很多地方写的代码还可能是为了某种特定场景专门定制,这巨大的信息差会使得初学者学习曲线过于陡峭。于此同时我觉得生啃源码的行为也让我们忘记了通过阅读Spring源代码来提升编码能力的初衷:Spring框架本身的一个设计理念是极好的,在代码里面也大量使用了各种设计模式,同时将面向对象使用的淋漓尽致。所以下面我会从对象的职责交互、使用到的设计模式两个方面阐释SpringIOC容器的初始化过程。

写在前面

本文将从已废除的XmlBeanFactory进行源码剖析,原因是它既简单又能够体现SpringIOC容器的具体初始化过程。 文章是以spring-framework:5.3.21为基础讲解,希望读者能够同步源码,跟着作者的思路一起阅读这份代码。 文章主体脉络如下:

  1. 回顾Spring使用XML文件依赖注入一个bean的例子
  2. 通过对象委托流程图描述这个例子内使用对象的一个交互情况
  3. 源码解读
    1. XmlBeanFactory如何通过XML文件初始化
    2. 如何通过XmlBeanFactory获取到一个单例bean

回顾Spring的Xml基础使用

估计现在大部分的人都开始使用SpringBoot进行业务开发,SpringBoot是个好框架。但是使用多了,容易忘了老本行。下面我们就一起回顾一下SPring的Xml基础使用吧

Demo

文章以Spring早已废除掉的XmlBeanFactory为例子讲述IOC的初始化过程,因为既简单又能体现IOC最基础的容器初始化过程。

先写一段入口程序

public class SpringApplication {
    
    public static void main(String[] args) {
        //读取beanFactory.xml并返回BeanFactory
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

        //通过beanFactory获取到helloWorld的实例
        HelloWorld helloWorld = (HelloWorld) beanFactory.getBean("helloWorld");

        //bean实例是否正常注入了name
        System.out.println(helloWorld.getName());
    }

}
复制代码

再写一个xml bean描述文件

<?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 id="helloWorld" class="com.lyj.spring.demo.HelloWorld">
        <property name="name" value="你好,世界"/>
    </bean>
</beans>
复制代码

通过上面的代码,java程序启动后,XmlBeanFactory会将beanFactory.xml进行解析,转换,实例化对象,注入属性值以供main方法使用(依赖注入)

对象委托流程图

spring框架大量使用了委托者模式、工厂模式以及模板模式。 而在BeanFactory的初始化过程中,委托者模式用的最多,这也是初始化代码难读的关键所在。下面我列了一份对象委托流程图

对象委托流程图.png

主要涉及四个类对象

  1. XmlBeanFactory
  2. XmlBeanDefinitionReader
  3. DefaultBeanDefintionDocumnentReader
  4. BeanDefinitionParseDeletegate

通过这四个类,主要的任务就是解析XML文件内容成为可供解读的BeanDefinition。最终真的在干解析任务的是BeanDefinitionParseDeletegate,其他对象都做一些异常检查和流程控制等工作。BeanDefinitionParseDeletegate解析完的结果将会把结果注入到XmlBeanFactory的成员变量中

源码解读

下面我们来阅读第一行代码

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

从这行代码中,我们可以看到beanFactory.xml这个文件名通过ClassPathResource的构造方法传入进去,并把ClassPathResource的实例通过XmlBeanFactory的构造方法传入到XmlBeanFactory中。很典型的委托者模式,XmlBeanFactory把读取beanFactory.xml文件流的工作交给了ClassPathResource,并把持有文件流的ClassPathResource引用交给XmlBeanFactory进行后续的工作。

接下来解读一下XmlBeanFactory的构造函数

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}
复制代码
  • 第一句为父类工厂的初始化工厂(A.xml通过import标签将B.xml引入,那么A就是B的父工厂),很明显,我们的xml没有父工厂,因和主流程无关,我们暂且跳过。
  • 第二句就是咱们文字的重点: this.reader.loadBeanDefinitions(resource); 这也是典型的委托者模式,XmlBeanFactory将Xml文件解析任务委托给了XmlBeanDefinitionReader

那么这里还有个问题,既然有委托的工作是希望XmlBeanDefinitionReader解析并返回可供XmlBeanFactory使用的元数据,那么如何将结果返回给XmlBeanFactory呢?

我们可以看下this.reader.loadBeanDefinitions(resource)这个reader的初始化语句

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
复制代码

通过这个初始化语句,我们可以看到XmlBeanFactory将自身引用交给了XmlBeanDefinitionReader。这里的目的很明显,就是在loadBeanDefinitions方法里面将解析好的结果通过this注册到XmlBeanFactory中。


下面我们继续剖析this.reader.loadBeanDefinitions(resource)这句话吧! 按照上面的对象委托图XmlBeanDefinitionReader也并没有真正解析,只是做了一些资源的封装和转换,将任务继续委托给DefaultBeanDefinitionDocumentReader

class XmlBeanDefinitionReader{
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
       //将ClassPathResource包装为EncodedResource
       //EncodedResource这个类原本是用来解决文件的字符集问题的,
       //但是在XmlBeanDefinitionReader中并没有用到这一功能,仅用了ClassPathResource也可提供的功能。
       //那么为什么引而不用?这其实是spring留给开发者的一个拓展点,当xml文件具有字符集转换需求时
       //直接重写loadBeanDefinitions(Resource resource)方法,并在实例化EncodedResource时
       //假如需要转换的字符集编码即可
       return loadBeanDefinitions(new EncodedResource(resource));
    }

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
       //省略了检查encodedResource和多线程加载xml文件的代码...

       //获取输入流
       try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
          //初始化inputSource
          //至于为什么要转,是因为spring要调用XAS库对xml文件节点进行解析,而XAS库仅支持inputSource输入流
          //但这并不影响咱们的理解,最终读的还是一个流
          InputSource inputSource = new InputSource(inputStream);
          if (encodedResource.getEncoding() != null) {
             inputSource.setEncoding(encodedResource.getEncoding());
          }
          //do开头的才是干实事的
          return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
       }

       //省略了一些异常处理代码...
    }
}
复制代码

经过上面的一大串代码,我们才完成了ClassPathResource转换成EncodedResource并获取到了InputSource的工作,真正解析的动作还得看doLoadBeanDefinitions。


class XmlBeanDefinitionReader{
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
          throws BeanDefinitionStoreException {

       try {
          //将流转换成Document文档,方便后面的解析工作
          Document doc = doLoadDocument(inputSource, resource);
          int count = registerBeanDefinitions(doc, resource);
          if (logger.isDebugEnabled()) {
             logger.debug("Loaded " + count + " bean definitions from " + resource);
          }
          return count;
       }
       //省略一大波异常语句
    }
}
复制代码

doLoadDocument这个方法主要做了xml文件校验的工作(DTD,XSD),不在本文的讨论范围内,后面会开一个课题单独讲解。下面我们将精力放到registerBeanDefinitions这个方法中,在这个方法中,XmlBeanDefinitionReader继续将解析任务委托给了DefaultBeanDefinitionDocumentReader

class XmlBeanDefinitionReader{
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
       //DefaultBeanDefinitionDocumentReader
       BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
       int countBefore = getRegistry().getBeanDefinitionCount();
       //委托者模式,包装上下文并将解析和注册工作又交给了DefaultBeanDefinitionDocumentReader
       //上下文中包含XmlBeanFactory的引用,能将解析结果直接注册到XmlBeanFactory中
       documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
       return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    
    public XmlReaderContext createReaderContext(Resource resource) {
       //通过构造函数,这个上下文环境包含着ClassPathResource、问题报告、事件监听、sourceExtractor、XmlBeanDefinitionReader、xml命名空间等引用
       //其中XmlBeanDefinitionReader又包含着XmlBeanFactory的引用
       return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
             this.sourceExtractor, this, getNamespaceHandlerResolver());
    }
}
复制代码

DefaultBeanDefinitionDocumentReader接到委托任务后,将任务分开了两个:节点遍历控制和节点转换 节点遍历在 parseBeanDefinitions(root, this.delegate);方法中 而节点转换则又继续委托给了BeanDefinitionParserDelegate

class DefaultBeanDefinitionDocumentReader{
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
       //将上下文保存到成员变量中,方便后面操作使用
       this.readerContext = readerContext;
       //读取节点,每个文件支持beans标签的嵌套,但不支持两个并列的beans标签
       //从这句话就能看出,只读了一个根节点
       doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    protected void doRegisterBeanDefinitions(Element root) {
       //一般来说,这个parent都为空,只有在beans标签内再内嵌beans标签的操作时才会有值
       BeanDefinitionParserDelegate parent = this.delegate;
       //创建更深的委托,去执行标签的解析;
       //如果parent不为空,那么证明这个调用来自于嵌套beans的解析,需要继承自身没有而 父类beans具有的默认属性
       this.delegate = createDelegate(getReaderContext(), root, parent);

       //省略一大波检查profile的代码...

       //模板设计模式,给开发者一次前置修改的机会
       preProcessXml(root);
       //真正处理解析的方法
       parseBeanDefinitions(root, this.delegate);
       //模板设计模式,给开发者一次后置置修改的机会
       postProcessXml(root);

       this.delegate = parent;
    }
}
复制代码

doRegisterBeanDefinitions(Element root)这个方法有两处调用,第一处是这里,第二处则来自于这个方法的递归处理(处理beans标签的地方),也就是说在createDelegate方法中,parent有值则一定来自于beans标签嵌套情况,而为空,就是没有beans嵌套情况。

image.png

下面将从没有beas嵌套情况继续说,当执行到parseBeanDefinitions方法的时候,我们可以看到根节点和处理解析的委托者delegate都传进了方法之中

class DefaultBeanDefinitionDocumentReader{
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
       //标准的spring标签
       if (delegate.isDefaultNamespace(root)) {
          NodeList nl = root.getChildNodes();
          //这里我们可以看到一个循环读取子节点的操作
          for (int i = 0; i < nl.getLength(); i++) {
             Node node = nl.item(i);
             if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                   //将子节点交给delegate进行解析
                   parseDefaultElement(ele, delegate);
                }
                else {
                //自定义的spring标签,如第三方实现的事务管理器标签。这个分支不在本文的讨论范围内
                   delegate.parseCustomElement(ele);
                }
             }
          }
       }
       //自定义的spring标签,如第三方实现的事务管理器标签。这个分支不在本文的讨论范围内
       else {
          delegate.parseCustomElement(root);
       }
    }
    /**
    * 解析import标签、alias标签、bean标签和beans嵌套标签
    * 下面重点举例讲解bean标签的解析
    **/
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
       //解析import标签
       if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
          importBeanDefinitionResource(ele);
       }
       //解析别名标签
       else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
          processAliasRegistration(ele);
       }
       //解析bean标签,重点举例解析
       else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
          processBeanDefinition(ele, delegate);
       }
       //解析嵌套的beans,就是我上文说到的递归调用的地方
       else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
          // recurse
          doRegisterBeanDefinitions(ele);
       }
    }

    /**
    *解析bean标签
    **/
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
       //将标签的属性转换为BeanDefinitionHolder
       BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
       if (bdHolder != null) {
          //给用户一次修饰BeanDefinitionHolder的机会
          bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
          try {
             // 将BeanDefinitionHolder注册到XMLBeanFactory中
             BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
          }

          //省略异常处理和事件触发钩子事件代码...
       }
    }
}
复制代码

代码讲了这么多,这里才真正开始进行单个标签的解析与注册工作,接下来继续逐个代码深挖

BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 这段代码调用了delegate的parseBeanDefinitionElement方法,并返回了spring容器里面可用的BeanDefinitionHolder实例对象,我们看看它delegate是如何解析的

class BeanDefinitionParserDelegate{
    /**
    *转换工作,真正干活的地方
    **/
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
       //获取id属性值
       String id = ele.getAttribute(ID_ATTRIBUTE);
       //获取name属性值
       String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

       //新建别名集合
       List<String> aliases = new ArrayList<>();
       //添加0或1或多个别名,供xmlBeanFactory使用
       if (StringUtils.hasLength(nameAttr)) {
          String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
          aliases.addAll(Arrays.asList(nameArr));
       }

       //id就是bean的实例名
       String beanName = id;
       //如果没有id,就会用别名中的一个个代替
       if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
          beanName = aliases.remove(0);
          if (logger.isTraceEnabled()) {
             logger.trace("No XML 'id' specified - using '" + beanName +
                   "' as bean name and " + aliases + " as aliases");
          }
       }

       //内嵌bean,类似于java里面的匿名函数,这里是匿名bean,containingBean为父bean的名称
       //如果不是匿名bean
       if (containingBean == null) {
          //就要检查一下beanName或aliases是否在容器中已经使用过了
          checkNameUniqueness(beanName, aliases, ele);
       }
       //将属性转为AbstractBeanDefinition,beanDefinition有多种类型,同样不在本文的讨论范围
       //我们只需要知道beanDefinition是用来存储bean的元数据即可
       //其中在本文讨论访问内的是bean对应的名称、全限定名class名或class对象
       //当然这个bean描述对象还保存是否有父类,是否是抽象类、泛型类、接口类,单例或原型,是否懒加载等等,
       //这些不是说不重要,只是不在本文讨论范围内而已
       AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
       if (beanDefinition != null) {
          if (!StringUtils.hasText(beanName)) {
             try {
                if (containingBean != null) {
                   //生成可以注册到XMLBeanFactory的bean名称
                   //这个名称和刚刚的id不同,这个的名称还包括了一下内部类等特殊类的特殊命名
                   beanName = BeanDefinitionReaderUtils.generateBeanName(
                         beanDefinition, this.readerContext.getRegistry(), true);
                }
                //省略不是太重要的代码...
             }
             //省略不是太重要的代码...
          }
          String[] aliasesArray = StringUtils.toStringArray(aliases);
          //包装者(装饰者)模式,在原来BeanDefinition的基础上,增加了经过修饰的bean名称,别名数组等信息
          return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
       }

       return null;
    }
}
复制代码

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);这段代码给了开发者一次修饰BeanDefinition的机会,不在本文讨论范围之内,主要用于在xml中难以描述的情况。

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());这段代码则是将我们已经解析好的bdHolder注册到XmlBeanFactory中

public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {

   // 经过处理的bean名称
   String beanName = definitionHolder.getBeanName();
   //将BeanDefinition插入XMLBeanFactory的成员变量map中
   //private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

   //注入到XMLBeanFactory的成员变量 别名Map中
   //private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
}
复制代码

至此,我们终于完成了XmlBeanFactory的初始化,现在的XmlBeanFactory已经初始化了beanDefinitionMap、aliasMap,能够支持后面的bean实例化工作

下一章我们将继续解读第二句代码 HelloWorld helloWorld = (HelloWorld) beanFactory.getBean("helloWorld"); 敬请期待!

总结

通过阅读XmlBeanFactory的初始化,我们学习到了委托者模式、模板模式在Spring中的运用,通过对象职责的层层委托,每个对象的职责渐渐变得分明。

猜你喜欢

转载自juejin.im/post/7129402246961299464