概述
Spring的爬坑之路(七)解析及注册BeanDefinilions
- registerBeanDefinitions
- instantiateClass
- doRegisterBeanDefinitions
- profile 属性的使用
- 解析并注册 BeanDefinition
文档二中,最后在方法doLoadBeanDefinitions中有代码为:
int count = registerBeanDefinitions(doc, resource);
我们当时了解其主要的作用是:根据返回的 Document 注册 Bean 信息。那么去转换的呢?我们梳理一下
registerBeanDefinitions
上一文中我们也已经了解把文件转换为 Document,接下来的提取及注册 bean就是我们的重头戏。 继续上面的分析,当程序已经拥有 XML 文档文件的 Document 实例对象时,就会被引人下面这个方法 。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
梳理一下逻辑:
- 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
- 将环境变量设置其中
- 在实例化BeanDefinitionReader 的时候会将BeanDefinitionRegistry传入默认使用继承自DefaultListableBeanFactory 的子类
- 记录本次加载的BeanDefinition个数
instantiateClass
这里在进入方法的时候,其中参数 doc 是通过上一节中loadDocument 加载转换出来的,在这个方法中很好地应用了面向对象中单一职责的原则1,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是 BeanDefinitionDocumentReader。 BeanDefinitionDocumentReader 是一个接口,而实例化的工作是在 createBeanDefinitionDocumentReaderQ中完成的,而通过此方法, BeanDefinitionDocumentReader 真正的类型其实已经DefaultBeanDefinitionDocumentReader 了,进入 DefaultBeanDefinition.Document
Reader 后,发现这个方法的重要 目的之一就是提取 root,以便于再次将 root 作为参数继续
BeanDefinition 的注册。
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
来了,来了,终于他来了…
如果说以前一直是 XML 加载解析的准备阶段,那么 doRegisterBeanDefinitions算是真正地开始进行解析了,我们期待的核心部分真正开始了…
doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
// 专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理profile属性
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;
}
通过上面的代码我们看到了处理流程,梳理下主要流程:
- 首先是对 profile 的处理,然后开始进行解析
- preProcessXml(root)、 postProcessXml(root)发现代码是空的。
既然代码为空,但又专程预留两个方法,并且方法中并没有用 final 修饰。这两个方法大概是为子类而设计。也就是我们设计模式中模版方法模式,如果继承自 DefaultBeanDefinitionDocumentReader 的子类需要 在 Bean 解析前后做一些处理的话,那么只需要重写这两个方法就可以了 。
profile 属性的使用
我们注意到在注册 Bean 的最开始是对 PROFILE_ATTRIBUTE 属性的解析,可能对于我们 来说, profile属性并不是很常用。 让我们先了解一下这个属性。
分析 profile前我们先了解下 profile 的用法, 官方示例代码片段如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<beans profile="dev">
... ...
</beans>
<beans profile="production">
... ...
</beans>
</beans>
集成到 Web环境中时,在 web.xml 中加入以下代码:
<context-param>
<param-name>Spring.profiles.active</param-口ame>
<param-value>dev</param-value>
</context-param>
有了这个特性我们就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境, 这样可以方便的进行切换开发、部署环境, 最 常用的就是更换不同的数据库 。了解了profile 的使用再来分析代码会清晰得多 , 首先程序会获取 beans 节点是否定义了 profile 属性,如果定义了则会需要到环境变量 中去寻找,所以这里首先断言 environment 不可能为空 ,因为 profile 是可以同时指定多个的,需要程序对其拆分,并解析每个 profile 是都符合环境变量中所定义的,不定义则不会浪费性能去解析 。
解析并注册 BeanDefinition
处理了 profile 后就可以进行 XML 的读取了,跟踪代码进入parseBeanDefinitions(root, this.delegate)。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
上面的代码看起来逻辑还是蛮清晰的,因为在 Spring 的 XML 配置里面有两大类 Bean 声明, 一个是默认的,如: < bean id=”test” class=”test.TestBean”/> 另一类就是自定义的,如: < tx:annotation-driven/>
而两种方式的读取及解析差别是非常大的,如果采用 Spring 默认的配 置 , Spring 当然知 道该怎么做,但是如果是自定义的,那么就需要用户实现一些接口及配置了。 对于根节点或者子节点如果是默认命名空间的话则采用 parseDefaultElement 方法进行解析,否则使用 delegate.parseCustomElement 方法对自定义命名空间进行解析。 而判断是否默认命名空间压是自定义命名空间的办法其实是使用 node.getNamespaceU阳()获取命名空间,并与 Spring 中固 定的命名空间 http://www.springframework.org/scherna/beans 进行比对。 如果一致则认为是默认,否则就认为是自定义 。
终于,整个流程我们算是梳理过一遍了,接下来的主题就是关于标签的解析了。我们接下来会一点一点攻克它。 革命还未成功,我们还是需要努力。继续加油~~
此处用到了BeanUtils.instantiateClass(Class clazz) ↩︎
最后
以上就是阳光康乃馨为你收集整理的Spring的爬坑之路(七)解析及注册BeanDefinilionsregisterBeanDefinitionsprofile 属性的使用解析并注册 BeanDefinition的全部内容,希望文章能够帮你解决Spring的爬坑之路(七)解析及注册BeanDefinilionsregisterBeanDefinitionsprofile 属性的使用解析并注册 BeanDefinition所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复