第二篇 再读Spring 之 BeanDefinition解析

第二篇 再读Spring 之 BeanDefinition解析



一、颗粒度问题

以Spring的XML配置文件为例,日常工作中现有applicationContext.xml 然后在文件中添加标签配置。显然就BeanDefinition加载而言,需要对Document(文档)和Element(元素)两种颗粒度的处理。特别地,我们的一个应用可能包含多个配置文件,多个配置文件包含了整个ApplicationContext中的bean。显然,这是一个更大的颗粒度,也就是容器级。从元素,文档到容器,我认为这是一次系统颗粒度的划分和识别,更抽象来说,这就是颗粒度问题。那为什么需要注意颗粒度问题?

不同颗粒级别就是对业务的一种拆分,拆分就意味着可能重用。毕竟软件工程的两大原则就是分解和重用。所以,良好的颗粒度识别和划分是系统设计或者架构的第一步。如果有看过UI/UE去设计网页,首先要做的就是页面布局,也就是页面上该划分几块,每块的内容是什么。如果用数学描述,这算是某种程度上的离散化。

因此,合理的颗粒度划分非常关键。Spring对BeanDefinition的加载处理就是按照这种颗粒度来组织加载逻辑的。

二、细说Spring中不同颗粒度对象在解析中的作用

1. 先说BeanDefinitionRegistry

想容纳所有的BeanDefinition,我们需要一个容器,这个容器提供方法,向其中加入/删除BeanDefinition,这就是BeanDefinitionRegistry,后续讨论中我们认为BeanDefinitionRegistry就是我们的BeanDefinition容器。从颗粒度的角度,这里是容器级。

BeanDefinitionRegistry中对应的方法是:

// 注册(添加)BeanDefinition
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;
// 删除BeanDefinition
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

2.(容器级)BeanDefinitionReader

仅仅拥有容器是不够的,因为没有实体调用registerBeanDefinition方法,去添加BeanDefinition。所以需要一个调用者,在Spring中就是BeanDefinitionReader,对于XML文件配置的应用来说,具体实现类为XmlBeanDefinitionReader. 该类掌控全局完成所有的BeanDefinition加载。之所以强调全局,是因为这里要放全局也就是跨XML文件级别的逻辑。具体有哪些呢?

  1. XML文件之间可以通过import建立关联,这里会有循环依赖,所以需要有循环依赖的侦测和处理逻辑。不过考虑到Spring通过Resource对各种配置形式做了抽象,所以准确说,应该是resource之间循环依赖的侦测和处理;
  2. 加载过程中的错误报告收集;
  3. 全局的自定义标签处理器NamespaceHandlerResolver;
  4. 全局的DocumentLoader,将Resource转换为Document;

3.(文件级)BeanDefinitionDocumentReader

具体到单个XML文件的处理,则是由BeanDefinitionDocumentReader来完成,具体实现是DefaultBeanDefinitionDocumentReader。这个类有3点需要注意:

  1. 预留了2个扩展方法,留给子类做扩展;
protected void preProcessXml(Element root) {}
protected void postProcessXml(Element root) {}
  1. 通过XmlReaderContext接收全局级别对象
    该类中并未持有对XmlBeanDefinitionReader的引用,而是通过XmlReaderContext间接访问到BeanDefinitionReader中的全局对象。如果是我来实现肯定在初始化DefaultBeanDefinitionDocumentReader时,把 BeanDefinitionReader 的this带过去。但是从OOP的角度,“局部"作为"下层"没必要知道自己在哪个"全局”(上层)中,所以直接带this是不合适的。既然没必要知道,那连Context也没必要要,为什么要加呢?后面再聊。

  2. 元素级别的上下文关联
    对于嵌套的Beans标签,在处理完成后,处理上下文需要恢复到包含Beans的上级标签。这似乎符合栈数据结构的使用场景,FILO。不过Spring不是这么做的,由于只有两级,直接使用parent记录父级上下文,Beans标签处理完成后,将当前处理上下文恢复。仔细想来,3个层级以内的,这么搞应该都是可以的。

BeanDefinitionDocumentReader的核心方法:

protected void doRegisterBeanDefinitions(Element root) {
// parent 缓存 当前delegete,然后当前delegate 更新为子级别的delegate 
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		
		// 预留的扩展方法
		preProcessXml(root);
		// 完成具体解析的方法
		parseBeanDefinitions(root, this.delegate);
		// 预留的扩展方法
		postProcessXml(root);

		this.delegate = parent;
	}

4.(标签级)BeanDefinitionParserDelegate

到了标签级别,除了自定义标签,主要有4大类,import, bean, beans 和alias. 这里除了import外,其他的标签都可以在当前上下文中解析。为啥import不行呢?

因为import指向的是另外一个XML文件,显然这里需要文件级别的对象提供支持,但是偏偏被标签级别的解析器处理。小马拉大车显然不行,考虑到逻辑重用,Spring得去重用文档级别上的处理逻辑,但是怎么比较符合OOP的方式调用到对应的方法是个问题。Spring是通过ReaderContext解决的,此处也解释了为什么需要1?

  1. 业务上有调用父级逻辑的需求;
  2. 站在OOP的角度,不能直接持有父级的引用,但需要一个暂存的对象来间接持有,这就是类Context对象的作用—关联上下层级;
  3. 有了Context之后,还可以把一些扩展逻辑放在其中;

类似的也可以考虑ApplicationContext,连接BeanFactory和程序对外的一些扩展功能。

三、颗粒粘合

对系统划分颗粒之后,还要把这种颗粒给粘合起来,否则就是一盘散沙。如何把颗粒之间的粘合起来,就是要确定颗粒之间的关联关系。也就是两个颗粒之间什么关系,是否需要关联的,该以何种方式关联。结合个人对Spring源码的阅读来聊聊这个问题。

将不同颗粒度的对象的关联为一个整体,就像树结构一样,从树根开始长出树枝,树枝上长出树叶。抽象到数据结构中,树枝是树根一级子节点,树叶则是树根的二级子节点,当然更复杂的可能有更多级。显然,每一级在逻辑上对应更小的颗粒度,上一层级关联下一层级,直到最小的不可再分割的颗粒度。

在实际工程中,关联有单双向区分。而类似BeanDefinition解析过程,需要下层访问上层,意味着双向访问。此外,有了双向访问后,同一个树根下的子节点可以借助树根调用到兄弟节点的逻辑。最终可能是,任何层级节点,可以调用其他层级的节点,这种自由度,可能是单纯的树结构无法做到的,当然也会更加复杂。

四、总结

以上就是今天要聊的内容,从颗粒化的作用,Spring中颗粒度的划分,层级之间双向关联的处理,最后到回归系统整体角度,看对不同层级颗粒对象的粘合。一方面对Spring BeanDefinition 的解析过程有一个清晰的脉络,也对设计模式的应用提供参考案例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值