以代码流程方式探索Spring源码--自定义BeanDefinition的解析
- 自定义标签是什么样儿的
- 根据当前解析标签element找到该标签对应的namespaceUri
- 加载spring中所有的jar包中的spring.handlers文件,并建立映射关系。
- 根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e81b32c2fe1b00b1b006dc8c1609cc0a.png#pic_center)
- 调用对应类的init()方法,该方法会注册对应的解析类
- 根据namespaceUri找到对应的解析类,调用其中的parse()方法完成自定义标签的解析
在Spring中,BeanDefinition的解析在将任务交由BeanDefinitonParserDelegate的时候,出现了分支。一支是spring默认标签的解析,另一支是自定义标签的解析,下面就探索下在spring初始化的过程中是怎么解析自定义标签信息的。
类:DefaultBeanDefinitonDocumentReader
自定义标签是什么样儿的
举例:<context:component-scan base-package=“com.csdn.wgd”/>
类:BeanDefinitionParserDelegate
从下图展示的parseCustomElement()方法中,可以看出解析自定义标签的三大步:1、获取namespaceUri;2、获取对应的NamespaceHandler;3、调用对应的parse()方法。
根据当前解析标签element找到该标签对应的namespaceUri
调用的是Node的方法,那这个得到的namespaceURI是什么呢,请看下图。
如上图所示,就是根据自定义标签的前缀在xmlns中找到对应的namespaceUri。
加载spring中所有的jar包中的spring.handlers文件,并建立映射关系。
以context这个自定义标签为例,它的spring.handler文件在如图所示的位置。
spring.handler文件中存储的是Element的namespaceUri和对应的handler类的映射关系。如下图所示:
根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e81b32c2fe1b00b1b006dc8c1609cc0a.png#pic_center)
那么映射关系是在哪里建立的的,跟进resolve()方法。
在这个方法中,首先会对spring所有jar包(包括自定义目录下面的)里面的 META-INFO/spring.handlers 文件中的内容建立map映射关系,然后获取到对应的handler类,执行 init() 方法。(下一个步骤介绍)
继续跟进getHandlerMappings()方法:
首先可以看到,这个方法进来会首先从DefaultNamespaceHandlerResolver的类变量中取这个map,取到的值为null时,才会去扫描文件进行解析,解析后会赋值给此变量,下一个方法再进来的时候就直接取值而不用解析了。这个也是spring中惯用的一种方法,利用类变量来实现缓存的功能,在我们自己的项目中有些需求也可以这样来实现。
至于对spring.handler文件的加载,跟进去就会发现底层也是用流的方法加载的。在spring中,对文件的读取本质底层都是用流的方式来加载。
调用对应类的init()方法,该方法会注册对应的解析类
下图中展示了context这个自定义标签对应的NamespaceHandler类。它的init方法注册了该前缀下可能存在的所有的具体标签的解析类。其中我们最常见的就是component-scan这个标签。我们可以发现在spring中,涉及到register的地方,基本就是建立一个映射关系,可以看出spring对map这种数据结构的使用很频繁,在我们自己的项目中,涉及到缓存之类的也可以使用这种数据结构。
根据namespaceUri找到对应的解析类,调用其中的parse()方法完成自定义标签的解析
类:NamespaceHandlerSupport
在这个地方获取到该element的具体解析器,就是在init()方法中注册的那些解析器。
在findParserForElement()方法中是通过该Element的localName来获取对应的解析Parser。存在一个map中,就是init的时候保存的map。
类:ComponentScanBeanDefinitionParser
在这里,先获取<context:component-scan base-package=“com.csdn.wgd”/>中的base-package属性。具体的扫描工作交给ClassPathBeanDefinitionScanner类的对象去做,就是doScan()方法,我们知道,前面带do的方法就是真正干活的方法。
在获取到BeanDefinitionHolder对象后,看上图最后一句代码,会进行注册,表面上看是注册到了XmlReaderContext中,实际上在XmlReaderContext中包含了BeanDefinitionRegistry。从类关系上可以看出来DefaultListableBeanFactory实现了BeanDefinitionRegistry。从前面的代码来看,这个地方的BeanDefinitionRegistry的实际类型是DefaultListableBeanFactory。此外,在这个地方还完成了三个重要的BeanDefinition的注册,ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotatinBeanPostProcessor。
我们先看下在configureScanner()方法中spring干了啥?
进来了先判断该component-scan标签有没有配置“use-default_filters”属性信息,如果没有配置的话,就使用spring默认的过滤器,如下图所示。默认操作就是在includeFilters这个List<TypeFilter>中加入@Component这个注解。这个就是我们的Spring默认扫描的时候认识的就是加有@Component注解的类的原因。
接下来我们看看doScan这个劳工到底是怎么干活的。
在循环中处理可能传过来的多个base-package的值;
我们可以看到在findCandidateComponents()方法中就返回了BeanDefinition类型的对象;
然后进行属性封装,最后注册进Beandefinition容器。
看一看是怎么根据base-package路径找到这些候选的BeanDefinition的。
从下图可以看出:
===>先对路径进行正则化;
===>递归路径找到该路径下的所有类文件并封装成Resource对象;【递归查找】
===>循环处理各个Resource;
===>抽取类的基本信息封装为MetadataReader对象;
===>判断师父需要创建BeanDefiontion,如果需要,则进行新建并放入set进行返回。
【递归查找方式是根据给的路径生成一个File,然后判断该File是否是一个.class文件,是的话加入候选,反之如果是一个文件夹,递归查找。】
【我们可以看到判断的逻辑是isCandidateComponent()方法,从名字上看就能看出来是判断类上有没有@Component注解,以及有没有对应的include和exclude属性并进行对应判断。】
【可以看出新建的BeanDefinition的实际对象类型是ScannedGenericBeanDefinition】
回到doScan()方法生成bd的后续操作
在第二个红箭头出对beanDefiniton进行了很多的属性的装配。
至此,我们的通过ClassPathXmlApplicationContext进来的spring初始化工作就完成了默认和自定义标签解析的BeanDefinition的注册工作。