以下源码版本是 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 就去parseDefaultElementfor (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)) {// recursedoRegisterBeanDefinitions(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的nameif (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) {// 上一层方法生成了beanNamethis.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作为keyregistry.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

Spring IOC原理 Bean标签解析和Definition封装相关推荐

  1. Spring Ioc原理及解析

    IOC简介 IoC是Inversion of Control的缩写,多数书籍翻译成"控制反转",还有些书籍翻译成为"控制反向"或者"控制倒置" ...

  2. spring源码深度解析— IOC 之 默认标签解析(下)

    默认标签中的自定义标签解析 注册解析的BeanDefinition 通过beanName注册BeanDefinition 通过别名注册BeanDefinition alias标签的解析 import标 ...

  3. Spring Ioc原理解析

    Spring Ioc原理解析 IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 图1:软件系统中耦 ...

  4. Spring IOC 原理解析

    Spring IOC 原理 ? IOC DI(依赖注入) **DI注入的几种方式** IOC容器 IOC原理 要想理解IOC,就需要知道一下IOC.DI 和IOC容器都是干什么的 IOC spring ...

  5. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  6. Spring IOC:bean的生命周期与@Autowire(1)

    全系列文章: <Spring IOC:bean的生命周期与@Autowire(1)> <Spring IOC:bean的生命周期与@Autowire(2)> <Sprin ...

  7. Spring IOC 原理

    Spring IOC原理 IOC理解 自己写的简单的IOC容器 IOC的原理 定位.加载.注册 基于XML配置的IOC容器初始化 1.寻找入口 2.获取配置路径 3.开始启动 4.创建容器 5.载入配 ...

  8. Spring IOC容器-Bean管理——基于XML方式

    Spring IOC容器-Bean管理--基于XML(续集) 1.IOC 操作 Bean 管理(FactoryBean) ​ 1).Spring 有两种类型 bean,一种普通 bean,另外一种工厂 ...

  9. Spring IOC之Bean初始化篇

    Spring Bean初始化简介 Spring IOC 是Spirng反向控制应用程序需要的资源,说白了就是类的实例化(new)操作交由Spring来进行管理.在Spring中创建的实例化对象我们称之 ...

最新文章

  1. 【linux kernel】 中断处理-中断下半部【转】
  2. Java写 soapclient_Java for Web学习笔记(一一八):【篇外】Soap client
  3. 全球云数据中心发展预测白皮书2016~2021
  4. 配置lamp+supervisor
  5. iOS 远程通知(Remote Notification)和本地通知(Local Notification)
  6. 不同管理岗层级的团队影响力_高影响力团队的最高要求
  7. 云服务器测速脚本_美国云主机哪家好?BlueHost美国云主机性能测评
  8. c++游戏开发案例源代码_1人开发千万下载,爆款游戏TENKYU调优案例
  9. 一名微博架构师的2016年终总结
  10. js动态添加HTML css失效,JS动态添加元素和设置其样式问题
  11. HITB AMS 2021 议题分析与学习,感叹华人真多
  12. java点歌系统_Java实现模拟KTV点歌系统
  13. 【笔记】用Python写百度翻译网络爬虫
  14. dimens文件生成器
  15. 初见TIC66XX系列DSP——C6678
  16. bzoj 2876: [Noi2012]骑行川藏 拉格朗日数乘
  17. CDH 6.3.1 集成Atlas
  18. Bootstrap知识点
  19. linux 定时任务相关配置问题
  20. java jwt刷新_基于springboot+jwt实现刷新token过程解析

热门文章

  1. WEBGIS实现限制地图拖动范围及缩放比例及extent()参数说明
  2. win_size exceeds image extent
  3. 【论文】:NEZHA(哪吒)
  4. 10、正则表达式 (笔试题、语法规则、正则对象方法、正则实例属性、支持正则表达式的String对象的方法、贪婪匹配与非贪婪匹配)
  5. 本地图文直接复制到百度Web编辑器中
  6. Cadence Orcad Capture 原理图设置标题框的方法
  7. 【C】例9.12口袋中有红、黄、蓝、白、黑5种颜色的球若干。每次从口袋中先后取出3个球, 问得到3种不同颜色的球的可能取法,输出每种排列的情况
  8. 格力珠海 生产运营管理 电话面试视频面试 拿到offer 面经
  9. c语言:结构体-查找书籍
  10. easyui之showFooter