XML解析的准备工作
- 使用ResourceLoader将资源文件路径转换为Resource文件。
- 封装资源文件。使用EncodedResource(resource、charset、encoding)类对参数Resource进行封装。
- 考虑Resource可能存在编码要求的问题
- 使用sax读取xml的方式来转呗InputSource对象。
- 获取输入流。从Resource中获取对应的InputStream并改造InputSource。
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
- 封装资源文件。使用EncodedResource(resource、charset、encoding)类对参数Resource进行封装。
- 通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Docuemnt文件。
- 获取XML文件的验证模式(DTD、XSD)。
- DTD:一种XML约束模式语言,是XML文件的验证机制,可以验证文件是否符合规范、元素和标签使用是否正确。
- XSD:XML Schema,验证XML文档检查XML文档是否符合要求。需要声明命名空间和命名空间存放的位置(schemaLocation)。
schemaLocation包含命名空间URI和schema文件位置或者URL地址。 - 通过判断是否包含DOCTYPE来判断验证模式,如果包含DOCTYPE,就是DTD,否则是XSD。
- 使用BeansDtdResolver来解析DTD文件中的实体(从Spring class path中加载),直接获取systemId最后的xx.dtd,然后在当前路径下寻找。
- 使用PluggableSchemaResolver默认获取META-INF/spring.schemas文件(properties文件)中找到systemid所对应的XSD文件并加载。
- 加载XML文件,并获取对应的Document文件。
- 使用单一指责的原则。
- 根据返回Document注册Bean信息。
- 获取XML文件的验证模式(DTD、XSD)。
- 通过DefaultBeanDefinitionDocumentReader(模版模式)对Document进行解析,并使用BeanDeifinitionParserDelegate对Element进行解析。
- 创建DefaultBeanDefinitionDocumentReader,调用registerBeanDefinitions方法
- 创建BeanDefinitionParserDelegate,初始化默认的:lazy-init, autowire, dependency check settings,init-method, destroy-method and merge settings,触发default-registered事件。
- 判断是否为默认的命名空间,如果是默认的命名空间,判断是否设置相应的profile。
- 对相应的标签进行解析(parseBeanDefinitions->parseDefaultElement),例如import、alias、bean、内置的beans,可以自定义在解析bean之前(preProcessXml)和之后(postProcessXml)的操作,,如果设置了自定义元素,需要走parseCustomElement方法。
解析XML文件
Import标签解析
- 获取resource属性所表示的路径,如果没有路径,跳出解析;
- 解析路径中的系统属性;
- 判断location是绝对路径还是相对路径;
- 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析;
- 如果是相对路径则计算出绝对路径进行解析;
- 触发import-process事件,解析完成。
alias标签解析
- 获取name属性和alias属性,如果有一个没有填写,跳过解析。
- 注册alias,触发alias-registered事件。
Bean标签解析
Bean标签解析的大致流程如下:
- 使用委托类BeanDefinitionParserDelegate的parseBeanDefinitionElement方法来进行元素解析,返回BeanDefinitionHolder类型的bdHolder实例,bdHolder实例包含配置文件中的各种属性。
- 返回的bdHolder实例不为空的情况下,如果存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
- 解析完成后,需要对解析后的bdHolder进行注册,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
- 通知相关的监听器,这个bean已经加载完成了。
解析BeanDefiniton
- 获取id、name属性,如果名字的长度比较长,然后按照',;'进行切分,放到集合中。
- 如果id属性为空以及name属性不为空,会输出没有具体id的log信息。并且将id当做beanName。
- 校验beanName和aliasName是否唯一,如果不是,输出error信息。
- 解析除了id,name之外的其他所有属性,并统一封装至GenericBeanDefinition类型的实例中(先通过class、parent属性创建GenericBeanDefinition对象实例)。
- 解析class、parent属性,并且创建出来GenericBeanDefinition对象实例。AbstractBeanDefinition是一个抽象类,实现BeanDefinition,
有具体实现类:RootBeanDefinition(没有父类<bean>的<bean>)、GenericBeanDefinition(一站式的BeanDefinition装配)、ChildBeanDefinition(子类bean)。
Spring 2.5之后,使用GenericBeanDefinition。BeanDefinitionRegistry是Spring配置信息的内存数据库,主要是以Map的形式保存。 - 解析Bean属性:singleton、scope、abstract、lazy-init、autowire、depend-on、autowire-candidate、primary、init-method、destory-method、factory-method、factory-bean。
- 在2.x全部使用scope,如果不指定,使用父类的scope范围。scope和singleton两个属性,指定指定一个。
- 使用kv构造BeanMetadataAttribute对象实例。
- 在需要的时候,获取一个新的bean,有两种方式:实现ApplicationContextAware接口;使用lookup-method标签。lookup标签可以动态的替换返回实体bean。
- @Lookup注解的作用是,当我当用一个方法时,Spring会返回一个方法返回值类型的实例。每次都会创建一个新的对象。
- @Lookup注解的应用场景:1.向单例bean注入一个prototype-scope的bean;2.使用程序方式注入依赖项。
- @Lookup注解的缺点:1.使用@Lookup注解的是必须是实体类不能是抽象类,因为扫描会跳过抽象类;2.如果使用@Bean-manage(工厂类不行)时,@Lookup注解不生效。子类不能是final的
- lookup注解在使用的过程中,需要有一个类来创建父类对象,在创建新的子类,要修改配置文件<lookup>的bean属性。
- replace-method标签: 在运行时用新的方法替换现有的方法。与look-up不同的是,replace-method不但可以动态的替换返回实体bean,而且还能动态地更换原有方法的逻辑。
- constructor-arg标签:获取index、type、name属性。
- 有index属性: 解析constructor-arg的子元素;使用ConstructorArgumentValues.ValueHolder来封装解析出来的元素,并添加到BeanDefinition的constructArgumentsValues的indexedArgumentValues属性中。
- 没有index属性:解析constructor-arg的子元素;使用ConstructorArgumentValues.ValueHolder来封装解析出来的元素,并添加到BeanDefinition的constructArgumentsValues的genericArgumentValues属性中。
- 跳过meta和description的解析;constructor-arg标签中,只能存在ref/value中的一个,并且不能有资源;使用RuntimeBeanReference封装ref属性;使用TypeStringValue封装value属性。
- 解析description属性。
- 解析meta标签,即meta标签,数据是键值对形式,并且生成BeanMetadataAttribute对象实例。
- 解析lookup-method标签,然后解析name和bean属性来生成lookupoverride对象实例。
- 解析replace-method标签,需要指定要替换的name和replacer属性、参数类型属性,生成ReplaceOverride对象实例。
- 解析construct-arg标签。
- 解析property标签
- 必须要有一个name属性
- 解析qualifier标签:指定注入bean的名称。
- 解析class、parent属性,并且创建出来GenericBeanDefinition对象实例。AbstractBeanDefinition是一个抽象类,实现BeanDefinition,
- 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成BeanName。
- 将获取到的信息封装到BeanDefinitionHolder实例中。
注册Bean
- 通过beanName注册BeanDefinition
- 对AbstractBeanDefinition的校验。主要是校验methodOverrides属性。
- 对beanName已经注册情况的处理。不存在,抛出异常;存在,覆盖。
- 加入map缓存。
- 清除解析之前留下的对应的beanName的缓存。
- 通过别名注册BeanDefinition
- alias与beanName相同情况处理。删除alias。
- alias覆盖处理。
- alias循环检查。
- 注册alias
加载bean
加载bean的过程:
- 转换对应的beanName。
- 去除FactoryBean的修饰符
- 去指定alias表示的最终beanName。
- 尝试从缓存中加载单例。
- singletonObjects:用于保存BeanName和创建bean实例之间的关系,bean name-->bean Instance。
- singletonFactories:用于保存BeanName和创建bean的工厂之间的关系,bean name-->objectFactory。
- earlySingletonObjects:用于保存BeanName和创建bean实例之间的关系,只保存单例的bean。用于解决bean的循环依赖引用。
- registeredSingletons:用来保存当前所有已注册的bean。
- bean的实例化。
- 对factorybean正确性的验证
- 对非factorybean不做任何处理。
- 对bean 进行转换。
- 将从Factory中解析bean的工作委托给getObjectFromFactoryBean。
- 获取单例
- 检查缓存是否已经加载过
- 若没有加载,记录beanName的正在加载状态。
- 加载单例之前记录加载状态。
- 通过调用参数传入的objectFactory的个体object方法实例话bean
- 加载单例后的处理方法调用
- 将结果记录至缓存并删除加载bean过程中记录的各种辅助状态。
- 返回处理结果。
- 准备创建bean
- 根据设置的clas属性或者根据className来解析Class。
- 对override属性进行标记及验证。
- 应用初始化的后处理器,解析指定bean是否存在初始化前的短路操作。
- 创建bean。
- 对后处理器中的所有InstantiationAwareBeanPostProcessor类型的后置处理器执行postProcessBeforeInstantiation方法和BeanPostProcessor的postProcessAfterInstantiation方法。
- 实例化前的后处理器应用:bean 实例化之前,就是将AbstractBeanDefinition转换为BeanWrapper前的处理。
- 实例化后的后置处理器应用:bean的初始化后尽可能保证注册的后处理器的postProcessAfterInitialization方法用到该bean中,因为如果返回的bean不为空,那么便不会再次经历普通bean的创建过程。
- 原型模式的依赖检查。
- 构造器循环依赖: 无法解决,只能抛出BeanCurrentlyInCreationException异常表示循环异常。
- setter循环依赖:只能解决单例作用域的bean循环依赖。
- prototype范围的依赖处理: 对于prototype作用域bean,Spring容器无法完成依赖注入,不进行缓存。
- 检测parentBeanFactory。
- 将GenericBeanDefinition转换为RootBeanDefinition。
- 寻找依赖。
- 针对不同的scope进行bean的创建。
- 类型转换。
创建bean
创建bean的过程:
- 如果是单例则需要清除缓存。
- 实例化bean,将BeanDefinition转换为BeanWrapper。
- 如果存在工厂方法,使用工厂方式初始化(存在factoryMethodName属性,或者配置文件中的factory-method)。
- 一个类有多个构造函数,根据参数锁定构造函数并进行初始化(用缓存机制)。
- 构造函数参数的确定: 根据explicitArgs参数判断(如果不为空,直接确定参数);缓存中获取;配置文件获取。
- 构造函数的确定:匹配的方法就是根据参数个数匹配,将public构造函数优先参数数量降序、非private构造函数参数数量降序。
- 获取参数名称的方式:注解方式直接获取;使用Spring中的ParameterNameDiscover来获取。
- 根据确定的构造函数转换为对应的参数类型。
- 构造函数不确定行的验证。
- 根据实例化策略得到的构造函数及构造函数参数实例化bean(动态代理)。
- 如果没有工厂方法和不带参数的构造函数,使用默认的构造函数进行bean的实例话。
- MergedBeanDefinitionPostProcessor的应用。
- 依赖处理。
- 属性填充。
- InstantiationAwareBeanPostProcessor处理器的postProcessAfterInstantiation函数的应用,此函数可以控制程序是否继续进行属性填充。
- 根据注入类型,提取依赖的bean,并统一存入PropertyValues中。
- 应用InstantiationAwareBeanPostProcessor的postProcessProertyValues方法,对属性获取完毕填充前对属性的再次处理,典型应用是RequiredAnnotationBeanPostProcessor类中对属性进行校验。
- 将所有PropertyValues中的属性填充只BeanWrapper中。
- 循环依赖检查。
- 初始化Bean(afterPropertiesSet、init-method属性)。
- 注册DisposableBean(destroy-method,DestructionAwareBeanPostProcessor的销毁方法)。
- 完成并返回。
创建bean使用的方式:反射和cglib代理。
Spring在获取bean的时候,使用map、和同步来记录加载的状态,也可以用来进行循环依赖检测。
使用InstantiationAwareBeanPostProcessor在bean实例化之前进行处理。
使用BeanPostProcessor在bean实例化之后进行处理。
网友评论