乐趣区

Spring-BeanDefinition的加载

 前面提到 AbstractRefreshableApplicationContext 在刷新 BeanFactory 时,会调用 loadBeanDefinitions 方法以加载系统中 Bean 的定义,下面将讲解 Bean 定义的加载过程。

一.XML 定义

 XML 配置的加载由 AbstractXmlApplicationContext 实现,方法实现如下:

 主要是实例化了一个 XmlBeanDefinitionReader 对象,对其设置了 Environment 对象(具体过程可以上一篇)等后,调用 XmlBeanDefinitionReader 进行解析。直接跟踪进去,得到如下内容:

 其中参数 inputSource 为 XML 配置文件,Resource 则该配置文件的描述,主要描述了该配置文件在 classpath 中的位置和可用于加载该配置文件的加载器。doLoadDocument 方法比较简单,主要是加载指定的配置文件,返回一个 JDK 内置的 XML 解析 Document 对象,以便于解析 XML DOM 节点。

 重点看下 registerBeanDefinitions 方法,如下:

 该方法最终委托给了 BeanDefinitonDocumentReader 来完成 Bean 的解析。在这之前,初始化了一系列相关对象。包括:

  1. 使用 DefaultBeanDefinitionDocumentReader 作为 BeanDefinitionDocumentReader 接口的实现
  2. 创建了一个 XmlReaderContext 用于保存解析过程中使用到的各个相关对象,如资源描述对象 Resource、ReaderEventListener 事件监听器、XmlBeanDefinitionReader 以及 NamespaceHandlerResolver 命名空间解析器。其中划重点的是 DefaultNamespaceHandlerResolver,该类完成了自定义 XML 格式的解析,后面会有讲解。

 初始相关对象后便将解析过程委托给了 DefaultBeanDefinitionDocumentReader 来进行处理,该类的重点为 parseBeanDefinitions 方法,在调用该方法前,先初始化了一个 BeanDefinitionParserDelegate 对象,如下:

 并将该 delegate 对象传入了 parseBeanDefinitions 方法。BeanDefinitionParserDelegate 主要提供了命名空间为 http://www.springframework.org/schema/beans(下面简写为 beans 空间)的 XML 文件解析过程。该命名空间定义了 4 个主要的 XML 标签,分别为beansbeanimportalias以及这些标签对应的属性,如下为该命名空间的示例,定义了一个基础的 Bean。

 需要注意的是,上面在初始化 BeanDefinitionParserDelegate 后会先解析 XML 上 <beans> 标签上的默认属性,包括:default-lazy-initdefault-mergedefault-autowiredefault-dependency-checkdefault-autowire-candidatesdefault-init-methoddefault-destroy-method 这些全局属性。

 下面看下 parseBeanBefinitoins 方法:

 该方法会对当前节点所属命名空间进行判断,分为默认命名空间和自定义命名空间,其中默认命名空间指的是上面提到的 beans 空间。对于默认命名空间,会逐一解析每个 DOM 子节点,判断子节点的命名空间,最终委托给两个方法:处理默认命名空间的 parseDefaultElement 方法和处理自定义命名空间的 parseClustomElement 方法。

 parseDefautlElement 方法如上,对 beans 空间定义的各个标签分别进行了处理:

  1. 解析 import 标签时,会读取 resource 属性指定的配置文件,加载后再解析该文件中的 bean 定义。
  2. 解析 alias 标签时,读取标签的 name 和 alias 属性,添加到 BeanRegistry 缓存中。
  3. 解析 bean 标签时,直接委托给 BeanDefinitionParserDelegate 来处理,过程为:

    1) 获取 id 属性值作为 beanName

    2) 获取 name 属性值作为 aliases,该属性值可以配置多个,以 , 或者 ; 符进行分割,将作为该 bean 的别名使用;若 id 值为空,且 name 不为空,则使用第一个 name 值作为 id 值

    3) 检查 beanName 和 aliases 的唯一性

    4) 解析 bean 其他配置,生成 GenericBeanDefinition 对象

    5) 若 beanName 为空,则为其分配一个

    对于步骤3.d,处理过程为:

    1) 获取 class 属性值

    2) 获取 parent 属性值

    3) 初始化 GenericBeanDefinition 实例

    4) 解析 bean 节点的属性,设置到 BeanDefinition 中,包括:scopeabstractlazy-initautowireddependency-checkautowire-candidateprimaryinit-methoddestroy-methodfactory-methodfactory-bean

    5) 解析 description 子节点,获取值设置 bean 的描述内容

    6) 解析 meta 子节点列表,获取 key、value 值设置附加元数据信息

    7) 解析 lookup-method 子节点列表,获取 name、bean 值设置方法注入信息

    8) 解析 replaced-method 子节点列表,获取值设置需要动态代理的方法信息

    9) 解析 constructor-arg 子节点列表,获取值设置构造参数信息

    10) 解析 property 子节点列表,获取值设置属性信息

    11) 解析 qualifier 子节点列表,获取值进行设置

 上面解析完 bean 的配置后,会再处理子节点中其他命名空间的配置,使用 NamespaceHandler 的 decorate 方法,用以修改 Bean 定义内容,这部分将使用下面的内容,放在后面一起讲。

  1. 解析 beans 标签时,会进行递归处理

 如上,默认命名空间主要用于解析 bean 的定义,经过上面的处理,bean 定义的解析就已经完成,会将实例对象注册到上下文中进行保存

 下面介绍 parseClustomElement 方法,顾名思义,该方法主要用来处理自定义命名空间的 XML 标签的,可以当做是 spring XML 配置处理的一种扩展手段,如下,为该方法的内容:

 主要委托给了 NamespaceHandlerResolver,通过查找到对应节点对应命名空间的 Handler,调用该 Handler 的 parse 方法进行处理。

 前面说过,NamespaceHandlerResolver 使用了 DefaultNamespaceHandlerResolver 作为实现,跟踪 resolve 方法进去如下:

 过程为:

  1. 获取已有的 Handler 处理列表,返回结果为一个 Map,Key 为 XML 命名空间,值可能为代表对应 Handler 类型的 Spring 对象或者已经实例化后的 Handler 对象,取决于之前是否已经调用过
  2. 若指为 Handler 对象,则直接返回
  3. 若为 String 对象,表明未初始化过,则初始化该类,并执行 init 初始化方法,然后将其重新返回 Map 中

 对于第(1)步中 Handler 处理列表的获取,Spring 会扫描 classpath 中所有位于 META-INF 中的 spring.handlers 配置文件,将所有内容读取到一个 Map 中并返回。

 如上,为 spring-context 模块提供的 spring.handlers 文件,提供了该模块自定义命名空间标签的支持。如下为自定义命名空间的例子:

 主要引入了 context 空间的 spring-configured 标签和 annotation-config 标签。

 至此,介绍了 XML 配置下的 bean 解析。

二、注解配置

 下面介绍 Spring 以注解的方式进行 bean 加载的过程,如下,为开启注解加载所需要的配置:

 根据前面的内容,component-scanhttp://www.springframework.org/schema/context 命名空间中的标签,处理对象在 spring-context 模块的 spring.handlers 文件中定义,对应的是类 org.springframework.context.config.ContextNamespaceHandler,如下:

 查看该类,可以知道,component-scan 由 ComponentScanBeanDefinitionParser 处理,如下:

 主要过程为:

  1. 获取 base-package 属性内容赋值给 basePackage
  2. 替换 basePackage 中的占位符内容
  3. 根据 , ; t n 分割符分割 basePackage,得到多个包路径
  4. 解析 component-scan 配置内容,返回 ClassPathBeanDefinitionScanner 对象

    1) 解析设置 use-default-filters 参数

    2) 解析设置 resource-pattern 参数

    3) 解析设置 name-generator 参数

    4) 解析设置 scope-resolver、scoped-proxy 等参数

    5) 解析设置 include-filterexclude-filter 等参数, ClassPathBeanDefinitionScanner 对象再初始化时默认增加了 org.springframework.stereotype. Component、javax.annotation.ManagedBean 和 javax.inject.Named 几种注解

  5. 调用 ClassPathBeanDefinitionScanner 的 doScan 方法执行扫描,将符合的类并注册到上下文中然后返回
  6. 解析 annotation-config 参数,如果为 true(默认为 true)则自动注册一系列用于后置解析的注解处理类定义到上下文中

这里重点看下第(5)步和第(6)步

 第(5)执行扫描时,会遍历第(3)步返回的所有路径,对于每个路径,会调用父类 ClassPathScanningCandidateComponentProvider 的 findCandidateComponents 方法,返回该路径下所有符合要求的 Bean,如下:

 ClassPathScanningCandidateComponentProvider 会找到指定路径所有的类,包装为 Resource[]对象,对于每个 Resource,会使用 SimpleMetadataReaderFactory 工厂类为每个 Resouce 对象新建一个 SimpleMetadataReader 对象,该对象用于解析类的信息,需要注意的是,在初始化 SimpleMetadataReader 对象的时候就会执行解析动作,将结果存为 ClassMetadata 数据和 AnnotationMetadata 数据,前者用于存储类的定义信息,包括类名,是否接口,包含的属性等信息,后者则包含所有的注解信息。获取 SimpleMetadataReader 对象后,会判断该类是否符合 component-scanner 定义的 include-filterexclude-filter中定义的内容,注意,默认包含了 @Component 等对象,所以默认会加载所有有 @Component 注解且所有有 @Component 元注解注解 (如@Service@Repository) 的类。若符合要求,则将该类包装为 ScannedGenericBeanDefinition 对象,同时会检查该类不能为一个接口且不能依赖一个内部非静态类,若符合,则添加到待返回列表中。

 执行完上面的 findCandidateComponents 方法后,会为其分配一个 beanName,用于内部使用,之后会调用 AnnotationConfigUtils 的 processCommonDefinitionAnnotations 方法,该方法会对上面返回的 BeanDefinition 解析一些基本的注解属性并进行设置,包括 @Lazy@Primary@DependsOn@Role@Description。完成该步后会判断该 bean 是否已经存在,若不存在,则添加到上下文中。

 第(6)步的代码如下:

 重点在后半部分,会调用 AnnotationConfigUtils 的 registerAnnotationConfigProcessors 方法,该方法添加了一系列用于后置解析的注解处理类定义到上下文中,包括:

  1. 添加 ConfigurationClassPostProcessor 处理器(BeanDefinitionRegistryPostProcessor), 添加 @Configuration 功能,对应 bean 为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
  2. 添加 AutowiredAnnotationBeanPostProcessor 处理器(SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加 @Autowired@Value 功能,对应 bean 为 org.springframework.context.annotation.internalAutowiredAnnotationProcessor
  3. 添加 RequiredAnnotationBeanPostProcessor 处理器(SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加 @Required 功能,对应 bean 为 org.springframework.context.annotation.internalRequiredAnnotationProcessor
  4. 添加 CommonAnnotationBeanPostProcessor 处理器(InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor、BeanPostProcessor),添加 @PostConstruct@PreDestroy 功能,对应 bean 为 org.springframework.context.annotation.internalCommonAnnotationProcessor
  5. 添加 PersistenceAnnotationBeanPostProcessor 处理器(InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加 @PersistenceUnit@PersistenceContext 功能,对应 bean 为 org.springframework.context.annotation.internalPersistenceAnnotationProcessor
  6. 添加 EventListenerMethodProcessor,添加 @EventListener 功能,对应 bean 为 org.springframework.context.event.internalEventListenerProcessor
  7. 添加 DefaultEventListenerFactory,对应 bean 为 org.springframework.context.event.internalEventListenerFactory

 以上各个 bean 在添加前都会先判断是否已经存在改定义,若不存在才增加,因而可以通过在添加相应 bean 的方式,修改对应的处理功能。

PS: 第(6)步添加注解后置处理的方法其实也是 annotation-config 这个标签功能的主要处理方法。由上可知 annotation-config 的处理类为 AnnotationConfigBeanDefinitionParser,该类内部其实也是调用了 AnnotationConfigUtils 的 registerAnnotationConfigProcessors 方法来完成注解的功能,具体代码如下:

三、接口回调

 结合之前 Spring 启动的内容,接上上面的内容,可以得到如下的接口回调顺序

 因为 InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor 等接口继承自 MergedBeanDefinitionPostProcessor 接口,MergedBeanDefinitionPostProcessor 接口继承自 BeanPostrProcessor,实现类上存在重叠,这里先根据讲解顺序排序。

个人公众号:啊驼

退出移动版