笔记整理自《Spring源码深度解析》(第2版),同时也参考了一些网上资源,具体参考链接在文末
整体分析
标签分类
- 默认标签
- alias 标签
- bean 标签
- beans 标签
- import 标签
- 自定义标签
bean标签的解析及注册流程
在默认标签中,对bean标签的解析最为复杂也最为重要,我们从此标签分析其解析流程,即可理解其他标签的解析
- 首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class,name,id,alias之类的属性。
- 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
- 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了Bean-
DefinitionReaderUtils的registerBeanDefinition方法。 - 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。
源码分析
parseBeanDefinitions 分析
DefaultBeanDefinitionDocumentReader.java |
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);
}
}
parseDefaultElement分析
DefaultBeanDefinitionDocumentReader.java |
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
doRegisterBeanDefinitions(ele);
}
}
对于不同类型的默认标签有不同的处理方法,接下来我们逐个分析
bean标签
(1)processBeanDefinition 方法解析
DefaultBeanDefinitionDocumentReader.java |
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
decorateBeanDefinitionIfRequired的作用是当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时,就需要进行装饰处理。
Spring 在这里将 bean 标签的处理完全交给了 BeanDefinitionParserDelegate 对象进行。
流程分析:
- 交由
BeanDefinitionParserDelegate 对象进行 bean 标签的处理得到 BeanDefinitionHolder 对象 - 将得到的
BeanDefinitionHolder 对象进行 Bean 定义注册 - 发布 Bean 注册事件
fireComponentRegistered为通知监听器解析以及注册完成,这里的实现只为扩展,如果需要对注册BeanDefintion事件进行监听注册时可以通过注册监听器的方式将处理器逻辑写入监听器中。
(2)parseBeanDefinitionElement分析
BeanDefinitionParserDelegate.java |
此方法较为复杂,我们分为几部分分析:
第一部分代码
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
...
}
第一部分的处理很简单获取 bean 标签中的 id 和 name 属性,对 name 属性做分隔符切分后将切分结果作为别名。
第二部分的代码
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
...
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
...
}
-
id 存在的情况下都是用 id 作为 Bean Name -
id 不存在的情况下使用 name 被分隔符切分后的第一个元素作为 Bean Name
第三部分的代码
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
...
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
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);
}
}
}
catch (Exception ex) {
...
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
注意其中
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
它会进一步解析其他所有属性,并统一封装至GenericBeanfinition类型的实例中。GenericBeanfinition是AbstractBeanDefinition的子类
BeanDefinition 是用于属性承载的接口,在 Spring 中存在三种实现: RootBeanDefinition 、childBeanDefinition 以及 GenericBeanDefinition
小结
-
提取元素中的id以及name属性。 -
进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
3、如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName
4、将获取到的信息封装到BeanDefinitionHolder的实例中。
拿到BeanDefinitionHolder实例后,接下来又回到了(1)方法中,我们可以看到接下来需要进行注册了
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
(3)注册解析的 Bean Definition
BeanDefinitionReaderUtils.java |
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
整个过程分为两部分;
- 第一步:通过beanName的注册
- 第二步:通过alias的注册
-
首先来分析第一步
DefaultListableBeanFactory.java |
private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
new ConcurrentHashMap<>(8);
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
...
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
...
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
...
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
很多人认为注册beanDefinition就是将beanDefinition直接放入map中,使用beanName作为key。不过除此之外,Spring还做了其他事情。具体流程如下:
- 对AbstractBeanDefinition的校验。之前的校验时针对于XML格式的校验,而此时的校验时针是对于AbstractBean-Definition的methodOverrides属性
- 对beanName已经注册的情况的处理。如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖
- 多线程中,如果有其他线程正在创建当前beanName,则尝试获取锁,获取成功进行注册,然后加入map缓存,加入beanName的list中。
- 无以上情况直接注册,将其加入map缓存。
- 清除解析之前留下的对应beanName的缓存。
-
分析第二步
public void registerAlias(String name, String alias) {
synchronized(this.aliasMap) {
if(alias.equals(name)) {
this.aliasMap.remove(alias);
} else {
String registeredName = this.aliasMap.get(alias);
if(registeredName != null) {
if(registeredName.equals(name)) {
return;
}
if(!allowAliasOverriding()) {
throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
}
}
checkForAliasCircle(name, alias);
this.aliasMap.put(alias, name);
}
}
}
整个方法以这样一个判断作为分支: 判断别名是否和真名相等,相等时候的处理很简单,从别名容器中剔除这个别名的键值信息。 接下来就是对于别名和真名不相等的情况处理了。 第一步:从别名容器中用别名去获取容器中可能存在的真名, 第二步:当容器中可能存在的真名存在的情况下会和参数真名进行比较是否相同 , 相同的话就直接返回了, 不做其他处理。如果参数不相同,且不允许覆盖的话,则报错 第三步:当容器中可能存在的真名不存在的情况下 Spring 会进行别名是否循环使用,当检测通过时加入到别名容器中。
alias标签解析
在对 bea 进行定义时,除了使用 id 属性来指定名称之外,为了提供多个名称,可以使用 alias 标签来指定 而所有的这些名称都指向同一个 bean ,在某些情况下提供别名非常有用,比 如为了让应用的每 个组件能更容易地对公共组件进行引用
我们回到parseDefaultElement方法中的processAliasRegistration中继续分析如何进行alias标签解析的
(4)processAliasRegistration解析
DefaultBeanDefinitionDocumentReader.java |
protected void processAliasRegistration(Element ele) {
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
...
}
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
import标签解析
applicationContext.xml文件中使用import的方式导入有模块配置文件,以后若有新模块的加入,那就可以简单修改这个文件了。这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理
我们回到parseDefaultElement方法中的importBeanDefinitionResource中继续分析如何进行import标签解析的
(5)importBeanDefinitionResource解析
DefaultBeanDefinitionDocumentReader.java |
protected void importBeanDefinitionResource(Element ele) {
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<>(4);
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
}
if (absoluteLocation) {
try {
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
...
}
catch (BeanDefinitionStoreException ex) {
..
}
}
else {
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
...
}
catch (IOException ex) {
...
}
}
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
流程分析:
- 获取resource属性所表示的路。
- 解析路径中的系统属性,格式如"S{user.dir}"
- 判定location是绝对路径还是相对路径。
- 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析。
- 如果是相对路径则计算出绝对路径并进行解析。
- 通知监听器,解析完成。
参考链接:
第三章 IoC 资源读取及注册
|