默认标签的解析是在parseDefaultElement函数中进行的,
// DefaultBeanDefinitionDocumentReader.java
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
// 对import标签的处理
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
// 对alias标签的处理
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
// 对bean标签的处理
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
// 对beans标签的处理
this.doRegisterBeanDefinitions(ele);
}
}
bean标签的解析及注册
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
}
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
- 通过BeanDefinitionParserDelegate类的parseBeanDefinitionElement进行元素解析,返回BeanDefinitionHolder,返回的实例包含了配置文件中配置的各种属性了,比如class、name、id、alias之类的属性
- 当第一步返回的实例不为空时,若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析
- 对第一步返回的实例进行注册,注册操作委托给了BeanDefinitionReaderUtils
- 发出响应事件,通过相关的监听器,这个bean已经加载完成了
解析BeanDefinition
// BeanDefinitionParserDelegate.java
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return this.parseBeanDefinitionElement(ele, (BeanDefinition)null);
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 解析id属性
String id = ele.getAttribute("id");
// 解析name属性
String nameAttr = ele.getAttribute("name");
// 分割name属性
List<String> aliases = new ArrayList();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
beanName = (String)aliases.remove(0);
if (this.logger.isDebugEnabled()) {
this.logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
this.checkNameUniqueness(beanName, aliases, ele);
}
//
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
// 如果不存在beanName那么根据Spring中提供的命名规则为当前bean生成对应的beanName
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
}
} catch (Exception var9) {
this.error(var9.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
} else {
return null;
}
}
- 提取元素中的id以及name属性
- 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中
- 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName
- 将获取到的信息封装到BeanDefinitionHolder实例中
BeanDefinition
BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三种实现均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件<bean>
元素标签在容器中的内部表示形式。
Spring通过BeanDefinition将配置文件中的<bean>
配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息。
解析默认标签中的自定义标签元素
接下来我们看bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
这一行代码,从方法名上看:如果需要的话就对beanDefinition进行装饰。
当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时,这句代码便会起作用了。
<bean id="test" class="test.MyClass">
<mybean:user username="aaa"/>
</bean>
这个自定义类型并不是以Bean的形式出现的。
具体的方法就不贴出来了,主要的条理是:首先获取属性或元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler并进行进一步解析。
总结一下decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfRequired中我们可以看到对于程序默认的标签的处理其实是直接略过的,因为默认的标签到这里已经被处理完了,这里只对自定义的标签或者说对bean的自定义属性感兴趣。在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理,并进行进一步的解析。
注册解析的BeanDefinition
对配置文件的解析和装饰都完成了,唯一剩下的就是注册了。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
// BeanDefinitionReaderUtils.java
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
// 使用beanName做唯一标识注册
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 注册所有的别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
解析的beanDefinition都会被注册到BeanDefinitionRegistry中,而对于beanDefinition的注册分成了两部分:通过beanName的注册以及通过别名的注册
通过beanName注册beanDefinition
对于beanDefinition的注册,就是讲beanDefinition直接放入map中就好了,使用beanName作为key。只不过除此之外,还做了点别的事情。主要进行了几个步骤:
1. 对AbstractBeanDefinition的校验。在解析XML文件的时候我们tguo校验,但是此校验非彼校验,之前的校验是针对于XML格式的校验,而此时的校验是针对于AbstractBeanDefinition的methodOverrides属性的。methodOverrides属性中记录了look-up,replaced-method等子元素
2. 对beanName已经注册的情况的处理。如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖
3. 加入map缓存
4. 清楚解析之前留下的对应beanName的缓存
通过别名注册beanDefinition
- alias与beanName相同情况处理。若alias与beanName名称相同则不需要处理并删除掉原有alias
- alias覆盖处理。若aliasName已经使用并已经指向另一个beanName则需要用户的设置进行处理
- alias循环检查。当A->B存在时,若再次出现A->C->B时候则会抛出异常
- 注册alias
通过监听器解析及注册完成
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
这里的实现只为扩展,当程序开发人员需要对注册BeanDefinition事件进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中。
alias标签的解析
在对bean进行定义时,除了使用id属性来指定名称外,为了提供多个名称,可以使用alias标签来指定。而所有的这些名称都指向同一个bean。
如配置文件中定义了一个JavaBean:
<bean id="testBean" calss="com.test" />
要给这个JavaBean增加别名,以方便不同对象来调用。我们就可以直接使用bean标签中的name属性:
<bean id="testBean" name="testBean1, testBean2" class="com.test" />
同样Spring还有另外一种声明别名的方式:
<bean id="testBean" calss="com.test" />
<alias name="testBean" alias="testBean1,testBean2" />
import标签的解析
对于Spring配置文件的编写,分模块是常用的技巧。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="customerContext.xml" />
<import resource="systemContext.xml" />
</beans>
使用import的方式导入模块配置文件,以后若有心模块的加入,那就可以简单修改这个文件,大大简化了配置后期维护的复杂度。
在解析import标签时,Spring进行解析的步骤大致如下:
1. 获取resource属性所表示的路径
2. 解析路径中的系统属性,如${user.dir}
3. 判定location是绝对路径还是相对路径
4. 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析
5. 如果是相对路径则计算出绝对路径并进行解析
6. 通知监听器,解析完成
嵌入式beans标签的解析
对于嵌入式beans标签来讲,与单独配置文件并没有太大差别,无非是递归调用bean的解析过程