栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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 xml中都有schema定义

            if (delegate.isDefaultNamespace(ele)) {

               parseDefaultElement(ele, delegate);

            }

            else {

                // 解析用户自定义的xml元素

               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属性。

// 这个方法是解析xml bean标签,产生beanName,封装beanDefintion对象,然后流程后面会注册bean

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {

    // 其实对已经用XAS框架解析了xml文件变成Element对象来说很容易,就是get(key)一样拿 id name 这些属性

   String id = ele.getAttribute(ID_ATTRIBUTE);

   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

   List aliases = new ArrayList<>();

    // 如果指定了  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 XML 'id' specified - using '" + beanName +

               "' as bean name and " + aliases + " as aliases");

      }

   }

   // 第一次解析xml封装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.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

      parseMetaElements(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的解析,真正的解析动作在BeanDefinitionParserDelegate.parseBeanDefinitionAttributes()中做的,都是硬编码通过get(key)方式拿到bean定义好的属性。

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

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

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1028349.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号