前面提到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,实现类上存在重叠,这里先根据讲解顺序排序。

个人公众号:啊驼

Spring BeanDefinition的加载的更多相关文章

  1. Spring源码加载BeanDefinition过程

    本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...

  2. 死磕Spring之IoC篇 - BeanDefinition 的加载阶段(XML 文件)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  3. Spring源码加载过程图解(一)

    最近看了一下Spring源码加载的简装版本,为了更好的理解,所以在绘图的基础上,进行了一些总结.(图画是为了理解和便于记忆Spring架构) Spring的核心是IOC(控制反转)和AOP(面向切面编 ...

  4. 深入Spring:自定义注解加载和使用

    前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑.特别是开发Web应用时,我们会频繁的定义@Controller,@Service ...

  5. 工厂模式模拟Spring的bean加载过程

    一.前言    在日常的开发过程,经常使用或碰到的设计模式有代理.工厂.单例.反射模式等等.下面就对工厂模式模拟spring的bean加载过程进行解析,如果对工厂模式不熟悉的,具体可以先去学习一下工厂 ...

  6. 监听器如何获取Spring配置文件(加载生成Spring容器)

    Spring容器是生成Bean的工厂,我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个 ...

  7. 【死磕 Spring】—— IoC 之 Spring 统一资源加载策略

    本文主要基于 Spring 5.0.6.RELEASE 摘要: 原创出处 http://svip.iocoder.cn/Spring/IoC-load-Resource/ 在学 Java SE 的时候 ...

  8. spring 配置文件被加载两次

    如下web.xml示例: 1.用spring的配置加载contextConfigLocation 2.配置spring-mvc的contextConfigLocation <servlet> ...

  9. 【Spring】Junit加载Spring容器作单元测试(整理)

    [Spring]Junit加载Spring容器作单元测试 阅读目录 >引入相关Jar包 > 配置文件加载方式 > 原始的用法 > 常见的用法 > 引入相关Jar包 一.均 ...

随机推荐

  1. 通过代码审计找出网站中的XSS漏洞实战(三)

    一.背景 笔者此前录制了一套XSS的视频教程,在漏洞案例一节中讲解手工挖掘.工具挖掘.代码审计三部分内容,准备将内容用文章的形式再次写一此,前两篇已经写完,内容有一些关联性,其中手工XSS挖掘篇地址为 ...

  2. 良许 | 网络检测神器 ss 命令,你可能根本就不会用!

    今天我们来介绍一下 Linux ss 命令. ss 是 Socket Statistics 的缩写,用来统计 socket 连接的相关信息,它跟 netstat 差不多,但有着比 netstat 更强 ...

  3. 林大妈的JavaScript进阶知识(三):HTML5 History API

    HTML5中新增了History API,它用于管理浏览器路由跳转的一个url栈.History是window对象的一部分,它也是一个对象,因此称它是BOM(类似DOM,Browser Object ...

  4. webstorm的live templates快速编辑功能,让你的css JS代码书写速度飞起来

    前言: Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来生成代码, 大大提高了HTML/CSS代码编写的速度,比如下面 ...

  5. 工作中遇到的99%SQL优化,这里都能给你解决方案(三)

    -- 示例表 CREATE TABLE `employees` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(24) NOT NULL ...

  6. H5 的 sessionStorage和localStorage

    1) H5 新增的 sessionStorage 和 localStorage 的区别 sessionStorage 和 java 的 session 差不多,可以短时间存储信息,电脑浏览器常用ses ...

  7. 【Redis】SpringBoot+Redis+Ehcache实现二级缓存

    一.概述 1.1 一些疑惑? 1.2 场景 1.3 一级缓存.两级缓存的产生 1.4 流程分析 二.项目搭建 一.概述 1.1 一些疑惑? Ehcache本地内存 Redis 分布式缓存可以共享 一级 ...

  8. Hello, OpenWrite

    目录 Markdown For Typora Overview Block Elements Paragraph and line breaks Headers Blockquotes Lists T ...

  9. 063 Python必备库-从人机交互到艺术设计

    目录 一.概述 二.Python库之图形用户界面 2.1 PyQt5 2.2 wxPython 2.3 PyGObject 三.Python库之游戏开发 3.1 PyGame 3.2 Panda3D ...

  10. SSM框架使用分页插件显示信息

    1.在该SSM工程的pom.xml中加入PageHelper的分页插件依赖(若是版本号不对,可以尝试修改一下) <!-- 分页插件 --> <dependency> <g ...