加载Bean
封装资源文件 对Resource使用EncodedResource进行封装
获取输入流,在Resource中获取对应的输入流
进入核心部分:数据准备阶段
获取对XML文件的验证模式
DTD:文本类型定义–>验证XML文档格式是否正确
XSD:XML schemas Defination –>验证XML文档格式是否正确
有了上面的两种xml文档的校验模式,spring会在进行校验模式的时候判断如果手动指定了验证模式(自定义的),那么就使用自定义的模式进行校验,如果没有指定则按照默认的验证校验模式。如果使用默认的校验模式,那么就会根据xml文件判断使用的是DTD还是XSD验证模式
加载XML文件,并得到对应的Document对象
EntityResolver:因为每次验证都会根据xml文件里面的声明去网上找相应的校验文件,这样会很费时间,因此使用这个方法将需要校验的文件直接保存在本地,在需要使用的时候直接从本地进行加载校验,而不需要从网上下载。
DTDResolver和SchemasResolver负责xml文件的解析
根据返回的Document注册Bean信息
doRegisterBeanDefinations这个方法就是解析的核心部分,这里在做解析的时候,前后使用了模板方法设计模式针对对Bean解析前后的相应处理
- profile属性:这个属性主要是指定了开发、部署环境,用这个属性可以指定想要的开发环境
- 解析注册BeanDefination,首先通过node.getnamespaceURI获取命名空间,然后通过对比spring中固定的命名空间,如果相同则使用默认的方法进行解析,如果不同则使用用户自定义的方法进行解析
默认标签的解析
这里会分四种情况,分别对import、alias、bean和beans做出不同的处理
Bean标签的解析及注册
首先委派BeanDefinationDelegate类的parseBeanDefinationElement方法进行元素解析,返回BeanDefinationHolder类型的实例,经过这个方法,bdHolder已经包含配置文件中配置的各种属性,例如:class、name、id、alias之类的属性
进入到parseBeanDefinationElement方法
- 提取元素中的id和name属性
- 进一步解析其他所有属性,并统一封装成GenericBeanDefination类型实例中
- 创建用于属性承载的BeanDefination:BeanDefination是一个接口,spring中有三种实现(RootBeanDefination、ChildBeanDefination、GenericBeanDefination)
- 解析各种属性(parseBeanDefinationAttributes),存到BeanDefination中
- 解析子元素(construct-args、property…)
- 如果检测到bean没有指定的beanName,那么使用默认规则为此Bean生成BeanName(通过beanClassName生成名字)
- 将获取到的信息封装到BeanDefinationHolder中
当返回的bdHolder不为空的时候,如存在默认标签的子节点下还有自定义的属性,还需要再次对自定义标签进行解析
寻找自定义标签并根据自定义标签寻找命名空间处理器,进行解析
解析完成之后,需要对解析后的bdHolder进行注册,注册操作委派给BeanDefinationReaderUtils的registerBeanDefination方法
- 通过BeanName注册
- 对AbstractBeanDefination校验methodOverrides属性
- 对BeanName已经注册过的情况,如果不允许Bean覆盖,那么就会抛出异常,否则覆盖
- 加入map缓存
- 清除解析之前留下的beanName缓存
- 通过别名注册
- alias与beanName相同不需要处理,删除原有的alias
- 若aliasName已经使用,那么进行覆盖操作
- 循环检查
- 注册alias
- 通过BeanName注册
最后发出响应事件,通知相关监视器,bean加载完成
alias标签解析
别名注册,就是将当前的Bean起多个别名,以适用于各种不同的场景
解析过程与Bean类似。。
impor标签解析
- 获取resource属性所表示的路径
- 解析路径中的系统属性
- 判断location是绝对路径还是相对路径
- 如果是绝对路径则递归调用bean的解析过程,执行另一次的解析
- 如果是相对路径,则计算出绝对路径进行解析
- 通知监视器完成
嵌入式Beans标签的解析
。。。
自定义标签的解析
获取标签的命名空间
getNamespaceURI
提取自定义标签处理器
通过Namespacehandler进行提取,这里就提到了之前自定义的处理器,如果命名空间与命名空间处理器有映射关系,那么就会从缓存中获取映射关系,如果不存在于缓存中,那么就使用自定义的处理器进行初始化后,存到缓存中
- 获取已经配置的handler映射
- 根据命名空间找到对应的信息
- 已经做过解析的情况直接从缓存中读取
- 没有做过解析,则返回类路径,使用反射将类路径转化为类
- 初始化类,调用自定义的NamespaceHandler的初始化方法
- 记录在缓存中
标签解析
得到解析器后,就会将工作委派给解析器去进行解析
Bean的加载
转换对应的BeanName
- 去除FactoryBean的修饰符
- 取指定的alias所表示的最终BeanName
尝试从缓存中加载单例
单例在spring的同一个容器中只会被创建一次,后续再获取bean直接从单例缓存中获取。这里只是尝试获取,首先尝试从缓存中加载,如果加载不成功再尝试去singeletonFactory中加载。因为在创建单例bean的时候,会存在依赖注入问题,因此为了避免依赖注入,在spring中创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提早曝光加入到缓存中,一旦下一个bean创建时候需要依赖上一个bean,直接使用Objectfactorybean的实例化
如果缓存中得到了bean的原始状态,那么就需要对bean进行实例化,从而得到需要的bean原型模型的依赖检查
只有在单例模式下才会进行循环依赖检查检查parentBeanFactory
如果缓存没有数据的话,就转到父类工厂去加载将存储XML文件的GenericBeanDefination转换为RootBeanDefination
后续操作都是针对RootBeanDefination,因此需要转换,如果BeanName是子bean的话,那么就会同时合并父类相关的属性寻找依赖
如果某些属性用到了其他属性,其他属性依赖其他的Bean,那么此时就需要递归查找加载依赖的bean
针对不同的scope进行bean的创建
针对不同的scope进行不同类型的初始化类型转换
循环依赖
什么是循环依赖?
就是两个或两个以上的bean,在方法中互相调用对方,然后他们之间的调用关系形成了一个环,这样的情况叫做循环依赖。
spring如何解决循环依赖的?
spring中循环以来包含构造器循环依赖和setter方法循环依赖
构造器循环依赖
通过构造器产生的循环依赖是无法解决的,只能抛出异常
因为在创建的过程中,比如有A、B、C三个bean对象,这三个对象互相调用对方,在创建A的时候,会在创建池中有标识A创建,但是创建过程中发现里面还有B创建,这时候就会先去创建B这个对象,然后在创建池中标识B的创建,但是B中又包含C的调用,这时候就需要创建C,然后在创建池中标识C的创建,但是这个时候C里面调用了A,这时候又会去创建A,但是在创建池中已经有A创建的标识,因此会直接抛出错误。(如果对象创建完成,则会将创建池中的标识清除掉)
setter循环依赖(只能解决单例模式下的循环依赖)
首先在创建A的时候,会先根据无参构造器,先创建一个bean,并且暴露一个ObjectFactory,用于返回一个提前曝光的一个创建中的bean,这样即使有循环依赖,但是因为创建的无参构造bean,即使在创建池中遇到这个依赖的bean,可以通过无参构造的bean进行返回,从而完成创建,并且不造成循环依赖
创建Bean
创建bean的实例
- 如果工厂方法不为空,那么使用工厂方法初始化策略
- 因为构造方法有很多参数,不同的参数需要不同的构造函数或对应的工厂方法
- 第2步如果进行了解析,那么就直接使用解析好的构造方法,不需要再次锁定
- 构造函数注入
- 默认构造函数构造
- 如果没有解析则需要根据参数解析构造器
- 构造函数注入
- 默认构造函数构造
- 构造函数注入
- 构造函数参数的确定
- 根据explicArgs参数判断
- 缓存中获取
- 配置文件获取
- 构造函数确定
- 根据确定的构造函数转换对应的参数类型
- 构造函数不确定性的验证
- 根据实例化策略以及得到的构造函数和构造函数的参数来实例化bean
- 构造函数参数的确定
- 不带参数的构造方法
- 第2步如果进行了解析,那么就直接使用解析好的构造方法,不需要再次锁定
- 实例化策略
使用过的设计模式
- 模板设计模式:在解析Bean的时候用过这个方法,主要是如果对解析Bean前后有相应的操作就会继承那两个方法进行操作。