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的解析。在这之前,初始化了一系列相关对象。包括:
使用DefaultBeanDefinitionDocumentReader作为BeanDefinitionDocumentReader接口的实现
创建了一个XmlReaderContext用于保存解析过程中使用到的各个相关对象,如资源描述对象Resource、ReaderEventListener事件监听器、XmlBeanDefinitionReader以及NamespaceHandlerResolver命名空间解析器。其中划重点的是DefaultNamespaceHandlerResolver,该类完成了自定义XML格式的解析,后面会有讲解。
初始相关对象后便将解析过程委托给了DefaultBeanDefinitionDocumentReader来进行处理,该类的重点为parseBeanDefinitions方法,在调用该方法前,先初始化了一个BeanDefinitionParserDelegate对象,如下:
并将该delegate对象传入了parseBeanDefinitions方法。BeanDefinitionParserDelegate主要提供了命名空间为http://www.springframework.org/schema/beans
(下面简写为beans空间)的XML文件解析过程。该命名空间定义了4个主要的XML标签,分别为beans
、bean
、import
和alias
以及这些标签对应的属性,如下为该命名空间的示例,定义了一个基础的Bean。
需要注意的是,上面在初始化BeanDefinitionParserDelegate后会先解析XML上<beans>
标签上的默认属性,包括:default-lazy-init
、default-merge
、default-autowire
、default-dependency-check
、default-autowire-candidates
、default-init-method
和default-destroy-method
这些全局属性。
下面看下parseBeanBefinitoins方法:
该方法会对当前节点所属命名空间进行判断,分为默认命名空间和自定义命名空间,其中默认命名空间指的是上面提到的beans空间。对于默认命名空间,会逐一解析每个DOM子节点,判断子节点的命名空间,最终委托给两个方法:处理默认命名空间的parseDefaultElement方法和处理自定义命名空间的parseClustomElement方法。
parseDefautlElement方法如上, 对beans空间定义的各个标签分别进行了处理:
- 解析import标签时,会读取resource属性指定的配置文件,加载后再解析该文件中的bean定义。
- 解析alias标签时,读取标签的name和alias属性,添加到BeanRegistry缓存中。
解析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中,包括:
scope
、abstract
、lazy-init
、autowired
、dependency-check
、autowire-candidate
、primary
、init-method
、destroy-method
、factory-method
、factory-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定义内容,这部分将使用下面的内容,放在后面一起讲。
- 解析beans标签时,会进行递归处理
如上,默认命名空间主要用于解析bean的定义,经过上面的处理,bean定义的解析就已经完成,会将实例对象注册到上下文中进行保存
下面介绍parseClustomElement方法,顾名思义,该方法主要用来处理自定义命名空间的XML标签的,可以当做是spring XML配置处理的一种扩展手段,如下,为该方法的内容:
主要委托给了NamespaceHandlerResolver,通过查找到对应节点对应命名空间的Handler,调用该Handler的parse方法进行处理。
前面说过,NamespaceHandlerResolver使用了DefaultNamespaceHandlerResolver
作为实现,跟踪resolve方法进去如下:
过程为:
获取已有的Handler处理列表,返回结果为一个Map,Key为XML命名空间,值可能为代表对应Handler类型的Spring对象或者已经实例化后的Handler对象,取决于之前是否已经调用过
若指为Handler对象,则直接返回
若为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-scan
为http://www.springframework.org/schema/context
命名空间中的标签,处理对象在spring-context模块的spring.handlers文件中定义,对应的是类org.springframework.context.config.ContextNamespaceHandler,如下:
查看该类,可以知道,component-scan由ComponentScanBeanDefinitionParser处理,如下:
主要过程为:
获取
base-package
属性内容赋值给basePackage替换basePackage中的占位符内容
根据 , ; \t \n 分割符分割basePackage,得到多个包路径
解析component-scan配置内容,返回ClassPathBeanDefinitionScanner对象
1) 解析设置use-default-filters参数
2) 解析设置resource-pattern参数
3) 解析设置name-generator参数
4) 解析设置scope-resolver、scoped-proxy等参数
5) 解析设置
include-filter
、exclude-filter
等参数, ClassPathBeanDefinitionScanner对象再初始化时默认增加了org.springframework.stereotype. Component、javax.annotation.ManagedBean和javax.inject.Named几种注解调用ClassPathBeanDefinitionScanner的doScan方法执行扫描,将符合的类并注册到上下文中然后返回
解析annotation-config参数,如果为true(默认为true)则自动注册一系列用于后置解析的注解处理类定义到上下文中
这里重点看下第(5)步和第(6)步
第(5)执行扫描时,会遍历第(3)步返回的所有路径,对于每个路径,会调用父类ClassPathScanningCandidateComponentProvider的findCandidateComponents方法,返回该路径下所有符合要求的Bean,如下:
ClassPathScanningCandidateComponentProvider会找到指定路径所有的类,包装为Resource[]对象,对于每个Resource,会使用SimpleMetadataReaderFactory工厂类为每个Resouce对象新建一个SimpleMetadataReader对象,该对象用于解析类的信息,需要注意的是,在初始化SimpleMetadataReader对象的时候就会执行解析动作,将结果存为ClassMetadata数据和AnnotationMetadata数据,前者用于存储类的定义信息,包括类名,是否接口,包含的属性等信息,后者则包含所有的注解信息。获取SimpleMetadataReader对象后,会判断该类是否符合component-scanner
定义的include-filter
和exclude-filter
中定义的内容,注意,默认包含了@Component
等对象,所以默认会加载所有有@Component
注解且所有有@Component
元注解注解(如@Service
、@Repository
)的类。若符合要求,则将该类包装为ScannedGenericBeanDefinition对象,同时会检查该类不能为一个接口且不能依赖一个内部非静态类,若符合,则添加到待返回列表中。
执行完上面的findCandidateComponents方法后,会为其分配一个beanName,用于内部使用,之后会调用AnnotationConfigUtils的processCommonDefinitionAnnotations方法,该方法会对上面返回的BeanDefinition解析一些基本的注解属性并进行设置,包括@Lazy
、@Primary
、@DependsOn
、@Role
和@Description
。完成该步后会判断该bean是否已经存在,若不存在,则添加到上下文中。
第(6)步的代码如下:
重点在后半部分,会调用AnnotationConfigUtils的registerAnnotationConfigProcessors方法,该方法添加了一系列用于后置解析的注解处理类定义到上下文中,包括:
添加ConfigurationClassPostProcessor处理器(BeanDefinitionRegistryPostProcessor),添加
@Configuration
功能,对应bean为org.springframework.context.annotation.internalConfigurationAnnotationProcessor添加AutowiredAnnotationBeanPostProcessor处理器(SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加
@Autowired
,@Value
功能,对应bean为org.springframework.context.annotation.internalAutowiredAnnotationProcessor添加RequiredAnnotationBeanPostProcessor处理器(SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加
@Required
功能,对应bean为org.springframework.context.annotation.internalRequiredAnnotationProcessor添加CommonAnnotationBeanPostProcessor处理器(InstantiationAwareBeanPostProcessor 、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor、BeanPostProcessor),添加
@PostConstruct
、@PreDestroy
功能,对应bean为org.springframework.context.annotation.internalCommonAnnotationProcessor添加PersistenceAnnotationBeanPostProcessor处理器(InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加
@PersistenceUnit
、@PersistenceContext
功能,对应bean为org.springframework.context.annotation.internalPersistenceAnnotationProcessor添加EventListenerMethodProcessor,添加
@EventListener
功能,对应bean为org.springframework.context.event.internalEventListenerProcessor添加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的加载的更多相关文章
- Spring源码加载BeanDefinition过程
本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...
- 死磕Spring之IoC篇 - BeanDefinition 的加载阶段(XML 文件)
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...
- Spring源码加载过程图解(一)
最近看了一下Spring源码加载的简装版本,为了更好的理解,所以在绘图的基础上,进行了一些总结.(图画是为了理解和便于记忆Spring架构) Spring的核心是IOC(控制反转)和AOP(面向切面编 ...
- 深入Spring:自定义注解加载和使用
前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑.特别是开发Web应用时,我们会频繁的定义@Controller,@Service ...
- 工厂模式模拟Spring的bean加载过程
一.前言 在日常的开发过程,经常使用或碰到的设计模式有代理.工厂.单例.反射模式等等.下面就对工厂模式模拟spring的bean加载过程进行解析,如果对工厂模式不熟悉的,具体可以先去学习一下工厂 ...
- 监听器如何获取Spring配置文件(加载生成Spring容器)
Spring容器是生成Bean的工厂,我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个 ...
- 【死磕 Spring】—— IoC 之 Spring 统一资源加载策略
本文主要基于 Spring 5.0.6.RELEASE 摘要: 原创出处 http://svip.iocoder.cn/Spring/IoC-load-Resource/ 在学 Java SE 的时候 ...
- spring 配置文件被加载两次
如下web.xml示例: 1.用spring的配置加载contextConfigLocation 2.配置spring-mvc的contextConfigLocation <servlet> ...
- 【Spring】Junit加载Spring容器作单元测试(整理)
[Spring]Junit加载Spring容器作单元测试 阅读目录 >引入相关Jar包 > 配置文件加载方式 > 原始的用法 > 常见的用法 > 引入相关Jar包 一.均 ...
随机推荐
- UGUI的图集处理方式-SpriteAtlas的前世今生
最糟糕的是人们在生活中经常受到错误志向的阻碍而不自知,真到摆脱了那些阻碍时才能明白过来. —— 歌德 说到UGUI的图集初学者可能觉得没什么难度,包括我刚开始接触的时候也是,甚至你在开发的时候只需要把 ...
- HDU 1848 Fibonacci again and again SG函数做博弈
传送门 题意: 有三堆石子,双方轮流从某堆石子中去f个石子,直到不能取,问先手是否必胜,其中f为斐波那契数. 思路: 利用SG函数求解即可. /* * @Author: chenkexing * @D ...
- Codeforces Round #486 (Div. 3)988D. Points and Powers of Two
传送门:http://codeforces.com/contest/988/problem/D 题意: 在一堆数字中,找出尽量多的数字,使得这些数字的差都是2的指数次. 思路: 可以知道最多有三个,差 ...
- hdu6356 Glad You Came 杭电多校第五场 RMQ ST表(模板)
Glad You Came Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others) ...
- NOIP 2005 等价表达式 题解
题意 给一个表达式然后再给n个表达式,判断是否等价 一道大模拟题,将a带为数,并且取模防止溢出 #include<bits/stdc++.h> using namespace std; c ...
- js-DOM ~ 05. Date日期的相关操作、string、查字符串的位置、给索引查字符、字符串截取slice/substr/substring、去除空格、替换、大小写、Math函数、事件绑定、this
内置对象: 语言自带的对象/提供了常用的.基本的功能 打印数组和字符串不用for... in / 打印josn的时候采用for...in Date 获取当前事件: var date = ...
- Tomcat性能调优参数简介
近期,我们的一个项目进入了试运营的阶段,在系统部署至阿里云之后,我们发现整个系统跑起来还是比较慢的,而且,由于代码的各种不规范,以及一期进度十分赶的原因,缺少文档和完整的测试,整个的上线过程一波三折. ...
- 【Redis】基础学习概览【汇总】
一.概述 1.1 简介 1.2 Redis单线程好处 1.3 单线程弊端 1.4 Redis应用场景 二.安装.开启以及关闭 三.Redis基本数据类型 四.SpringBoot整合Redis 五.R ...
- 【LeetCode】75-颜色分类
题目描述 给定一个包含红色.白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色.白色.蓝色顺序排列. 此题中,我们使用整数 0. 1 和 2 分别表示红色.白色 ...
- idea控制台乱码解决方案
第一步:修改intellij idea配置文件: 找到intellij idea安装目录,bin文件夹下面idea64.exe.vmoptions和idea.exe.vmoptions这两个文件,分别 ...