Spring IOC原理 Bean标签解析和Definition封装

以下源码版本是 Spring 5.2.x

IOC Inversion of Control 控制反转,关键实现是DI Dependency Injection,就必然涉及到有一个容器保存系统中所有托管的bean。

那么Spring是如何找到这些托管的bean呢?关键有以下几步:

  1. 读取xml配置文件转换成Resource(现在流行的SpringBoot是另一套体系,但底层应该还是脱离不了Spring)
  2. 利用XAS框将Resource解析成Document对象,方便对各种标签的解析提取解析Document对象,封装成BeanDefinitions
  3. 将各类BeanDefinition注册进BeanRegistry

先看看关键的类 DefaultListableBeanFactory XmlBeanFactory,DefaultListableBeanFactory是整个bean加载的核心部分,是注册及加载bean的默认实现,XmlBeanFactory继承DLF,大部分对bean的解析和注册是在DFL实现的,XBF只是用了XmlBeanDefinitionReader读取xml配置文件。

两者的类图:

XmlBeanDefinitionReader是读取xml文件、解析及注册的主要类,继承自AbstractBeanDefinitionReader中的方法,使用ResourceLoader将文件转换成Resource文件。

通过DocumentLoader对Resource文件进行转换成Document。用DefaultBeanDefinitionDocumentReader类对Document进行解析。

我们通常用这样的代码来启动一个bean工厂,这个工厂里就会包含了该配置文件里的所有托管对象。

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("application.xml"));

Spring定义了Resource接口抽象成内部使用到的底层资源:File, URL, Classpath 等等对应不同的Resource有不同的实现类,FileSystemResource,ClasspathResource,UrlResource,InputStreamResource,ByteArrayResource等

代码中的ClassPathResource实现也很简单, this.clazzLoader.getResourceAsStream(this.path) 通过这行代码在classpath路径下寻找对应的文件读取文件流返回。

可以看到XmlBeanFactory类没什么内容,只是增加了对xml读取配置文件的支持。

当封装好了Resource 剩下的解析和注册工作就交给XmlBeanDefinitionReader进行。

可以看到当resource被注入到factory的构造函数中,用reader开始读取resource并解析,这里是资源加载的开始。

在reader的 doLoadBeanDefinitions() 方法中先把文件流转成document,再解析document对象注册bean

这里用到的 DocumentBuilderFactory DocumentBuilder 都是 javax.xml.parsers 包下的类,也就是说Spring用JDK自带的技术解析XML。SAX 全称 Simple Api For Xml

loadDocument方法做了3件事:

  1. 创建 DocumentBuilderFactory
  2. 用factory创建DocumentBuilder
  3. 用builder解析文件流生成document

接着到 registerBeanDefinitions方法中,去解析doc注册bean,可以发现documentReader.registerBeanDifinitions() 源码中重要的地方之一是拿到root对象

documentReader引用的实例是DefaultBeanDefinitionDocumentReader类,在 registerBeanDefinitions() 方法中最关键的就是下面三行,其中 preProcessXml() 和 postProcessXml() 是空方法,是模板设计模式,留着给子类继承后去重写。

真正解析xml文件的开始,这里分为两个方向,对spring默认标签的解析和自定义标签解析。

protected void parseBeanDefinitions(Element root BeanDefinitionParserDelegate delegate) {
   if (delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
    // 遍历所有子节点,判断是默认标签,bean import alias 就去parseDefaultElement
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            Element ele = (Element) node;
            // 解析spring的自带标签,自带标签在spring xm<x>l中都有schema定义
            if (delegate.isDefaultNamespace(ele)) {
               parseDefaultElement(ele delegate);
            }
            else {
                // 解析用户自定义的xm<x>l元素
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}
解析默认标签
private void parseDefaultElement(Element ele BeanDefinitionParserDelegate delegate) {
    // 如果当前子元素是import元素
   if (delegate.nodeNameEquals(ele IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
    // 如果当前子元素是alias元素
   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标签为代表最有意义,全部解析bean标签的工作都在BeanDefinitionParserDelegate 类中完成。主要就是通过枚举硬编码获取 Element对象的bean属性。

// 这个方法是解析xm<x>l bean标签,产生beanName,封装beanDefintion对象,然后流程后面会注册bean
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele @Nullable BeanDefinition containingBean) {
    // 其实对已经用XAS框架解析了xm<x>l文件变成Element对象来说很容易,就是get(key)一样拿 id name 这些属性
   String id = ele.getAttribute(ID_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   List<String> aliases = new ArrayList<>();
    // 如果指定了 <bean id='xx' name='abc' /> name属性,将它分割成别名数组,默认用Id做bean的name
   if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
   }
   String beanName = id;
   if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
      beanName = aliases.remove(0);
      if (logger.isTraceEnabled()) {
         logger.trace("No xm<x>l 'id' specified - using '" + beanName +
               "' as bean name and " + aliases + " as aliases");
      }
   }
   // 第一次解析xm<x>l封装beanDefiniton时,这里为空
   if (containingBean == null) {
       // 检查beanName唯一性,保证IOC容器中所有beanName不冲突
      checkNameUniqueness(beanName aliases ele);
   }
    // 重点,将代表bean元素的对象解析成 bean定义,这时已经包含了 id name class等属性
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele beanName containingBean);
   if (beanDefinition != null) {
      if (!StringUtils.hasText(beanName)) {
         try {
             // 第一次解析时这里为空
            if (containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition this.readerContext.getRegistry() true);
            }
            else {
               beanName = this.readerContext.generateBeanName(beanDefinition);
               // Register an alias for the plain bean class name if still possible
               // if the generator returned the class name plus a suffix.
               // This is expected for Spring 1.2/2.0 backwards compatibility.
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }
         }
         catch (Exception ex) {
            error(ex.getMessage() ele);
            return null;
         }
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition beanName aliasesArray);
   }
   return null;
}
// 真正解析bean标签,封装defintion的地方
public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele String beanName @Nullable BeanDefinition containingBean) {
    // 上一层方法生成了beanName
   this.parseState.push(new BeanEntry(beanName));
   String className = null;
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }
   String parent = null;
   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
   }
   try {
    // 得有一个容器装bean属性吧,所以先创建一个 GenericBeanDefinition实例承载各种bean属性
      AbstractBeanDefinition bd = createBeanDefinition(className parent);
    // 这里就是各种get(key)硬编码的方式获取 element 中的bean属性,包括scope,init-method  lazy-init,destory-method等属性
      parseBeanDefinitionAttributes(ele beanName containingBean bd);
      bd.setDesc<x>ription(DomUtils.getChildElementValueByTagName(ele DEsc<x>riptION_ELEMENT));
      parseme<x>taElements(ele bd);
      parseLookupOverrideSubElements(ele bd.getMethodOverrides());
      parseReplacedMethodSubElements(ele bd.getMethodOverrides());
      parseConstructorArgElements(ele bd);
      parsePropertyElements(ele bd);
      parseQualifierElements(ele bd);
      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));
      return bd;
   }
   ... 省略了catch代码块
   finally {
      this.parseState.pop();
   }
   return null;
}

基本类似于这种先判断存在就对其做处理,设置到bean定义中

GenericBeanDefinition是AbstractBeanDefinition的子类实现,xml bean标签的所有属性都可以在其中找到。执行完BeanDefinitionDelegate.parseBeanDefinitionElement(ele) 方法解析完bean所有属性得到bean定义。回到DefaultBeanDefinitionDocumentReader.processBeanDefinition() 方法中,下一步就是对bean定义的注册。

BeanDefinitionRegistry 相当于Spring配置信息的内存数据库,数据结构是map

public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {
   // Register bean definition under primary name.
   String beanName = definitionHolder.getBeanName();
    // 可以看到bean定义将会被注册到 BeanDefinitionRegistry中,用beanName=bean id作为key
   registry.registerBeanDefinition(beanName definitionHolder.getBeanDefinition());
   // 注册bean的别名集合,可以通过别名找到beanName,再找到bean,相当于二级索引
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName alias);
      }
   }
}

BeanDefinitionRegistry是一个接口,它有3个实现,其中DefaultListableBeanFactory是xml配置默认的使用类。也就是说DefaultListableBeanFactory是xml配置的默认Bean注册器,bean定义最后被保存到成员变量中

到这里完成了对xml文件的读取成ClasspathResource,用SAX框架转成Document拿到root,在DefaultBeanDefinitionDocumentReader.parseBeanDefinitions() 中遍历所有子元素,它的子元素包括了 <bean/> <import/> <tx:/> 这种默认标签和自定义标签。

解析其中的默认标签和自定义标签。对于默认标签重点看了bean的解析,真正的解析动作在BeanDefinitionParserDelegate.parseBeanDefinitionAttributes()中做的,都是硬编码通过get(key)方式拿到bean定义好的属性。

封装到GenericBeanDefinition对象中,回到DefaultBeanDefinitionDocumentReader.processBeanDefintion() 中,

下一步对bean定义注册到 DefaultListableBeanFactory中(它是BeanDefinitionRegistry)。这一步完成后就回到DefaultBeanDefinitionReader遍历root元素的循环中,遍历下一个子元素进行解析。

OK 这里只是有了bean定义,bean定义是如何被实例化成托管对象,再解决托管对象的依赖注入问题的?

这就是另外的知识了。可以看另一片文章,IOC原理 加载bean

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值