Spring IoC 源码系列(一)BeanDefinition 初始化与注册
一、BeanDefinition
1.1 什么是 BeanDefinition
在一般的 Spring 项目中,主要通过 XML 的方式配置 bean,而 BeanDefinition
就是 XML 配置属性的载体,XML 文件首先会被转化成 Document
对象,通过解析 Document
,把 XML 中 <bean />
标签转化成 BeanDefinition
供 IoC 容器创建 bean 时使用。
我们可以来做个测试。
<bean id="typeMismatch" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"><property name="name"><value>typeMismatch</value></property><property name="age"><value>34x</value></property><property name="spouse"><ref bean="rod"/></property></bean>
下面是 debug 后抓到的 BeanDefinition
属性。
1.2 BeanDefinition 初始化流程图
二、源码分析
Debug 测试类入口:org.springframework.beans.factory.xml.XmlBeanDefinitionReaderTests#withOpenInputStream
。
下面只是把核心流程拿出来作了分析,一些细节知识点,有兴趣的可以自行了解。
测试代码如下
@Test(expected = BeanDefinitionStoreException.class)public void withOpenInputStream() {/*** 注意这里初始化的是 SimpleBeanDefinitionRegistry 不具备 BeanFactory 功能* 仅仅用来注册 BeanDefinition,不能用来创建 bean*/SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml"));new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource);}
loadBeanDefinitions
源码如下
@Overridepublic int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {// 使用 EncodedResource 包装 Resource,EncodedResource 可以指定字符集编码return loadBeanDefinitions(new EncodedResource(resource));}public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, "EncodedResource must not be null");if (logger.isTraceEnabled()) {logger.trace("Loading XML bean definitions from " + encodedResource);}// 获取已经加载过的资源集合Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {// 初始化 currentResourcescurrentResources = new HashSet<>(4);// 设置初始化的 currentResourcesthis.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");}try {// 根据 Resource 获取输入流InputStream inputStream = encodedResource.getResource().getInputStream();try {InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// 核心逻辑,加载 bean 资源return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}catch (IOException ex) {throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}}
2.1 获取输入流
测试类中定义的是 InputStreamResource
,下面 InputStreamResource
中 getInputStream()
的实现。
@Overridepublic InputStream getInputStream() throws IOException {InputStream is;if (this.clazz != null) {is = this.clazz.getResourceAsStream(this.path);}else if (this.classLoader != null) {is = this.classLoader.getResourceAsStream(this.path);}else {is = ClassLoader.getSystemResourceAsStream(this.path);}if (is == null) {throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");}return is;}
2.2 转化 Document
对象
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 获取 XML 对应 document 实例Document doc = doLoadDocument(inputSource, resource);// 调用 registerBeanDefinitions 方法注册 BeanDefinitionsint count = registerBeanDefinitions(doc, resource);if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex);}}
doLoadBeanDefinitions
方法中调用 doLoadDocument
初始化 Document
对象,内部实现比较简单,下面一起来看一下。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());}
上面涉及到几个关于 XML 的知识点,下面简单的介绍一下,最后有列出参考文章,有兴趣的可以翻翻。
EntityResolver
:XML 文件解析器errorHandler
:解析出错处理机制getValidationModeForResource()
: 获取 XML 验证格式,XML 一般支持 DTD 与 XSD,也可以自定义,主要用来约束与验证 XML 文档格式isNamespaceAware()
:判断解析器是否支持解析当前 XML 文件,<beans xmlns=""/>
其中xmlns
就是命名空间
@Overridepublic Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {// 创建 DocumentBuilderFactoryDocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isTraceEnabled()) {logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");}// 创建一个 DocumentBuilderDocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);// 以 SAX 形式解析 XMLreturn builder.parse(inputSource);}
上面 DocumentBuilderFactory
与 DocumentBuilder
都是 JDK 中提供的类,根据 XML 输入流获取 Document
的过程没有深入跟踪,这里就不展开分析了。
2.3 处理 Document
节点
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 通过反射创建一个 BeanDefinitionDocumentReader 对象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 获取已经注册的 BeanDefinition 的数量,-> beanDefinitionMap 的 sizeint countBefore = getRegistry().getBeanDefinitionCount();// 创建 XmlReaderContext,注册 BeanDefinitionsdocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 返回最新注册的 bean 的数量return getRegistry().getBeanDefinitionCount() - countBefore;}@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;doRegisterBeanDefinitions(doc.getDocumentElement());}protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);// 检查 <beans> 标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beansif (this.delegate.isDefaultNamespace(root)) {// 获取 profile 的值,beans 标签可以设置 profile 属性用于多环境配置管理String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {// 处理 profile 多个值String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);// 判断是否有默认启用的 profileif (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}// 解析前处理,空实现,可自定义preProcessXml(root);// 解析 document 实例parseBeanDefinitions(root, this.delegate);// 解析后处理,空实现,可自定义postProcessXml(root);this.delegate = parent;}protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 命名空间检查,Spring XML 中不仅可以配置 <beans /> 标签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)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {// samples:<tx:annotation-driven>delegate.parseCustomElement(root);}}private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {// import,samples:<import resource="classpath:/org/springframework/beans/factory/xml/test.xml"/>if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {// 根据 resource 值定位资源,递归调用 loadBeanDefinitions 逐个加载importBeanDefinitionResource(ele);}// aliaselse if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {// 处理别名标签,最终注册到 aliasMap 中processAliasRegistration(ele);}// beanelse if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {// 转化成 BeanDefinitionprocessBeanDefinition(ele, delegate);}// beanselse if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// 递归调用doRegisterBeanDefinitions(ele);}}
在获取 Document
对象后,后续流程会遍历所有子节点,根据子标签名分别走不同的处理流程,我们主要是来了解怎么初始化与注册 BeanDefinition
的,其他标签就不详细介绍了。
- 利用反射创建
BeanDefinitionDocumentReader
对象 - 获取已经注册的
BeanDefinition
的数量,其实就是beanDefinitionMap
的 size 大小 - 检查
<beans />
标签命名空间是否为空,或者配置为http://www.springframework.org/schema/beans
,如果条件满足,获取profile
指定的环境属性值,判断指定的环境是否有处于启用状态的,都不启用直接返回,不会对Document
进行解析,关于profile
属性的作用,最后会给一些参考文章 - 解析前置处理,空实现,可自定义
- 如果是默认命名空间,获取 下所有子标签,进行遍历,判断子标签是
import
、alias
、bean
还是beans
- 解析后置处理,空实现,可自定义
2.3 BeanDefinition 初始化
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 处理 <bean /> 标签,把标签属性与子标签信息封装在 BeanDefinition 中BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 装饰 BeanDefinitionbdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// Register the final decorated instance.// 注册 BeadDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// Send registration event.// 发送解析注册完成响应事件,通知相关的监听器getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);}@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {// <bean name="b1,b2,b3" /> id 与 name 的属性功能类似,name 属性支持创建多个别名// 获取 id 属性String id = ele.getAttribute(ID_ATTRIBUTE);// 获取 name 属性String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);// 处理 name 属性的别名List<String> aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}// 优先使用 id 属性的别名String beanName = id;// 如果 id 属性为空,且 name 属性不为空使用 name 属性中的第一个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");}}// 需要检查 beanName 的唯一性,避免与其他 bean 重复if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}// 获取 AbstractBeanDefinition 实例,该对象中保存了 <bean /> 标签中的基本属性信息AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {// TODO 定义一个 bean 并不一必须定要为其定义 id、name 属性// 如果没有定义 id 或者 name 属性,则想办法生成 beanNameif (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {/*** 使用 className 属性生成 beanName* beanName + # + beanDefinition 哈希值的十六进制字符*/beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isTraceEnabled()) {logger.trace("Neither XML 'id' nor 'name' specified - " +"using generated bean name [" + beanName + "]");}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}// 构造 BeanDefinitionHolder 对象并返回String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}@Nullablepublic AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {// 将 beanName 存储到一个 LinkedList 中this.parseState.push(new BeanEntry(beanName));// 记录 classNameString className = null;// 获取 class 属性if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;// 判断是否有 parent 属性if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}try {// 创建用于承载 XML 属性配置的 AbstractBeanDefinition 实例AbstractBeanDefinition bd = createBeanDefinition(className, parent);// 解析 <bean /> 标签的各种属性,比如 scope、lazy-init、autowire 等parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);// 解析描述信息bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));/*** 以下处理 <bean /> 的子标签,这里可以优化,循环一次对子标签分类处理* 下面每处理一个子标签就需要循环一次 <bean /> 所有的子标签*/// 处理 <meta/>parseMetaElements(ele, bd);// 解析 lookup-method 属性 <lookup-method />parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析 replaced-method 属性 <replaced-method />parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析构造函数参数 <constructor-arg />parseConstructorArgElements(ele, bd);// 解析 property 子元素 <property />parsePropertyElements(ele, bd);// 解析 qualifier 子元素 <qualifier />parseQualifierElements(ele, bd);// 设置 Resource 实例bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);}finally {this.parseState.pop();}return null;}
初始化 BeanDefinition
的过程就是把 <bean />
标签及子标签的属性保存到 BeanDefinition
中,没有什么复杂的逻辑。
2.4 注册 BeanDefinition
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.// 获取 beanName 并根据 beanName 注册 BeanDefinitionString beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.// 从 BeanDefinition 中获取所有的别名,并根据 beanName 注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {// 注册所有的别名,保存到 aliasMap 中registry.registerAlias(beanName, alias);}}}@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "'beanName' must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");// 保存到 beanDefinitionMap 中this.beanDefinitionMap.put(beanName, beanDefinition);}
因为测试类中定义的是 SimpleBeanDefinitionRegistry
,因此应该定位到 SimpleBeanDefinitionRegistry
中的 registerBeanDefinition
,这里的处理流程很简单,直接把 BeanDefinition
与 beanName
关联保存到 beanDefinitionMap
中。
SimpleBeanDefinitionRegistry
并不是一个工厂,不具备初始化 bean 的能力。后面在创建 bean 的流程中还会接触到 DefaultListableBeanFactory#registerBeanDefinition
注册流程,稍微比这个复杂些,但是其核心逻辑都是保存到 beanDefinitionMap
。
参考阅读
XML中DTD,XSD的区别与应用
xsd,dtd,tld有什么区别和联系?
细说java解析XML文档的常用方法(含实例)
详解Spring中的Profile
Spring IoC 源码系列(一)BeanDefinition 初始化与注册相关推荐
- Spring IoC 源码系列(五)getBean 流程分析
一.FactoryBean 用法讲解 在分析源码流程之前,我们先来看一下 FactoryBean,乍一看这家伙和 BeanFactory 很像,它们都可以用来获取 bean 对象,简单来说 Facto ...
- Spring IoC 源码系列(四)bean创建流程与循环依赖问题分析
创建单例 bean 的代码细节在 org.springframework.beans.factory.support.AbstractBeanFactory#getBean 中,getBean 顾名思 ...
- Spring IoC 源码系列(三)Spring 事件发布机制原理分析
在 IoC 容器启动流程中有一个 finishRefresh 方法,具体实现如下: protected void finishRefresh() {clearResourceCaches();init ...
- Spring IoC 源码导读
源码记录:spring-framework-5.1.7-source-code-read 文章导读 Spring IoC 源码系列(一)BeanDefinition 初始化与注册 Spring IoC ...
- Spring源码系列:BeanDefinition载入(下)
在Spring源码系列:BeanDefinition载入(上)中已经大概捋了一下解析过程,本篇将记录一下bean的注册过程. bean的注册就是DefaultListableBeanFactory中r ...
- Spring IoC源码:getBean 详解
文章目录 Spring源码系列: 前言 正文 方法1:getObjectForBeanInstance 方法2:getObjectFromFactoryBean 方法3:doGetObjectFrom ...
- Spring读源码系列之AOP--03---aop底层基础类学习
Spring读源码系列之AOP--03---aop底层基础类学习 引子 Spring AOP常用类解释 AopInfrastructureBean---免被AOP代理的标记接口 ProxyConfig ...
- Spring源码系列:BeanDefinition源码解析
Bean的定义主要由BeanDefinition来描述的.作为Spring中用于包装Bean的数据结构,今天就来看看它的面纱下的真容吧. 首先就是BeanDefinition的类定义: public ...
- spring源码系列一--BeanDefinition
如果说java是由对象组成,那么spring-framework框架可以说是由BeanDefinition所构成.BeanDefinitiion其实是spring中的顶级接口,我们在阅读源码之前必须要 ...
最新文章
- 机器学习与数据科学 基于R的统计学习方法(基础部分)
- 盘点深度学习一年来在文本、语音和视觉等方向的进展,看强化学习如何无往而不利
- mysql数据迁移 脚本_PHP将数据从Oracle向Mysql数据迁移实例
- 计算机算法知识总结,移动笔试知识点之--计算机类-数据结构与算法知识点总结.pdf...
- leetcode - 53. 最大子序和
- win10我的电脑在哪里找到
- ActiveMQ 使用文档
- bzoj 1930: [Shoi2003]pacman 吃豆豆 [费用流]
- SQL Server 2008无日志文件附加数据库
- 近6年被引用次数最多的深度学习论文top100(附下载地址)
- android 仿站小工具,仿站小工具下载
- kali2020 中文乱码问题
- 2021年中青杯 B题 港珠澳车辆通行(详细解题思路)
- 免费企业网站模板_学校网站模板_政府网站模板源码下载
- python头像转卡通_用python将你的头像“卡通化”
- 移动云平台的基础架构之旅(一):云应用
- 单、双目相机标定及其校正相关函数整理
- 为micropython添加模块(2)-类模块
- SecureCRT显示乱码的解决办法(centos)
- 拓扑序列(拓扑排序)
热门文章
- No MyBatis mapper was found in ‘[xx.mapper]‘ package. Please check your configuration
- 牛客题霸 NC27 集合的所有子集
- JavaScript——易班优课YOOC课群在线测试自动答题解决方案(一)答案获取
- Nginx——域名|端口|目录请求转发配置DEMO
- BugKuCTF WEB web基础$_GET
- SUM and REPLACE
- linux压缩和打包的区别,Linux中的压缩和打包
- JavaScript基础06-day08【if练习、条件分支语句switch、for循环】
- 【CentOS Linux 7】【Linux系统及应用---调研报告】
- 01-CoreData简介