背景简介
出现的原因
我们上面介绍了BeanDefinitionParser
接口。这个接口虽然根据它的定义,是可以直接针对某一个确定的元素进行处理。
但是这种操作还是比较混杂的,包括了解析和注册两个操作。所以又进行了进一步的细化:因为注册操作是可预测的,所以直接用模版方法给搞定了,用户只需要根据自己的情况完成元素到 BD 的解析操作,并根据情况决定是否需要进行对应的别名注册、事件通知、id生成。
职责
完成基本的 BD 注册、别名注册、事件通知、id生成等功能。并将 BD 的生成委托给子类实现。
注意点
源码
继承关系
1.png源码
// 日常,模版方法模式
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
// 将生成 BD 的逻辑放在方法中委托子类实现
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) { // 如果不是嵌套的 BD ,就注册
try {
// 这里封装获得 id 的逻辑
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = null;
if (shouldParseNameAsAliases()) { // 用户自行实现,看是否要注册别名
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) { // TODO: 注意啦!这里它是默认进行字段的拆分的,用的",",默认命名空间的是",;"都行
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) { // 用户自行实现,看是否要事件通知
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
} catch (BeanDefinitionStoreException ex) {
String msg = ex.getMessage();
parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
return null;
}
}
return definition;
}
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
if (shouldGenerateId()) { // 用户自行实现,看是否需要自动生成id,如果不需要就先从属性中读
return parserContext.getReaderContext().generateBeanName(definition);
} else {
String id = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) {
// 用户自行实现,属性没读到是否要自动生成id来做兜底方案
id = parserContext.getReaderContext().generateBeanName(definition);
}
return id;
}
}
基本注释都说明白了,可以实现的接口如下:
BD 生成逻辑的封装接口:
parseInternal
:用户自行实现,用于创建要注册的BeanDefinition
用于控制BD注册策略的接口:
shouldParseNameAsAliases
:是否要为 BD 注册别名shouldFireEvents
:是否要将 BD 生成、注册的通知给监听器shouldGenerateId
:是否跳过element
配置,直接生成 IdshouldGenerateIdAsFallback
:未读到id属性,是否以生成的id为兜底方案
根据各自的情况自行实现即可。
总结记录
可以参考此类的一些设计思想:
- 对于一些复杂功能的接口,在实现时尽量进行功能的抽离
- 对于一些可有可无的环节,可以参考上面的控制方法进行封装
问题
此处虽然进行了封装,但我感觉封装的并不是很友好,可以进一步改进:
- 如果是我封装这个接口,我会把
Id
,Name
字段摘出来抽成一个实例变量或者方法,让子类在定义时自行实现 - 如果是我封装这个接口,我会把
shouldGenerateId
,shouldGenerateIdAsFallback
去掉,直接先读Id
,读不到再生成,因为:- 在 Spring 中,肯定是先从配置入手的
- 如果有需要,可自行设置
Id
的非空校验,但是建议还是和 Spring 命名空间的逻辑保持一致 - 按照正常人的思路来,奇异思路和刻意搞怪就是给后人挖坑
- 在
id
最终获得为空的情况下,没有用alias
别名去填坑,这个 Spring 命名空间的逻辑不通,这到后面都是坑
扩展
自行实现一个命名空间的定制和解析,参考 “Spring 思路解析中的自定义命名空间解析" 一篇。
网友评论