BeanDefinition注册-下篇

512 阅读6分钟

BeanDefinition注册-下篇

前言

接着上文,此文主要讲了import和alias标签的解析,另外补充IOC容器注册的一些细节。

importBeanDefinitionResource

处理import标签大致步骤:

  1. 获取resource配置的地址

  2. 按照《BeanDefinition资源加载》中的方法解析资源

  3. 发送一个ImportProcessed事件。

protected void importBeanDefinitionResource(Element ele) {
    //获取 resource 属性值
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    if (!StringUtils.hasText(location)) {
        getReaderContext().error("Resource location must not be empty", ele);
        return;
    }
    
    //如果resource是表达式的话,就解析成普通的路径
    location = getEnvironment().resolveRequiredPlaceholders(location);

    Set<Resource> actualResources = new LinkedHashSet<Resource>(4);

 //确认resource是不是绝对路径
    boolean absoluteLocation = false;
    try {
        //判断是不是绝对路径
        //如果以classpath*: 或 classpath: 开头就是绝对路径
        //或一个绝对的URL
        absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    }
    catch (URISyntaxException ex) {
        // cannot convert to an URI, considering the location relative
        // unless it is the well-known Spring prefix "classpath*:"
    }

    //如果是绝对路径,就进入
    if (absoluteLocation) {
        try {
            //这一步直接调用了XmlBeanDefinitionReader.loadBeanDefinitions方法,所以就是重复之前的步骤,这里不再赘述。
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
            if (logger.isDebugEnabled()) {
                logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
            }
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error(
                "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
        }
    }
    else {
        // 相对路径的处理策略
        try {
            int importCount;
            //首先获取按照相对路径去获取资源
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            //如果存在,还是和上面一样进行解析
            if (relativeResource.exists()) {
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            }
            else {
                //如果获取相对路径的资源失败,就使用URL获取相对路径资源
                String baseLocation = getReaderContext().getResource().getURL().toString();
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                    StringUtils.applyRelativePath(baseLocation, location), actualResources);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
            }
        }
        catch (IOException ex) {
            getReaderContext().error("Failed to resolve current resource location", ele, ex);
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
                                     ele, ex);
        }
    }
    //把成功加载的资源,放置到actualResources中
    Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
    //并分发一个ImportProcessed事件
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

processAliasRegistration

处理alias标签,alias的处理比较简单,首先获取alias和name属性值,然后注册到IOC容器中,最后发送一个AliasRegistered事件

protected void processAliasRegistration(Element ele) {
    //获取 name 和 alias 属性值
    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 {
            //直接向IOC容器注册name 和 alias,具体注册,见下文
            getReaderContext().getRegistry().registerAlias(name, alias);
        }
        catch (Exception ex) {
            getReaderContext().error("Failed to register alias '" + alias +
                                     "' for bean with name '" + name + "'", ele, ex);
        }
        //发送一个AliasRegistered事件
        getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
    }
}

BeanDefinition在IOC容器中的注册

IOC是怎么管理BeanDefinition?这就要从DefaultBeanDefinitionDocumentReader说起:

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

//调用了静态方法
public static void registerBeanDefinition(
    BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    throws BeanDefinitionStoreException {

    //获取beanName
    String beanName = definitionHolder.getBeanName();
    //把beanName和BeanDefinition注册到容器中,下面继续探究
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
  
    //注册别名
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String aliase : aliases) {
            //下面继续探究别名的注册
            registry.registerAlias(beanName, aliase);
        }
    }
}

BeanDefintion的注册

BeanFactory的默认实现是 DefaultLisableBeanFactory,Spring中大部分情况下也都是使用DefaulltLisableBeanFactory作为基本的IOC容器实现。

DefaultLisableBeanFactory 时BeanFactory等接口的集大成者,我会单独写一篇文章来讲解,暂定名称为《IOC默认容器-DefaultLisableBeanFactory》。

在此篇中,我只简单地分析下注册BeanDefinition这一方法

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");
	//根据上文,我们此处获取的BeanDefinition的类型时GenericBeanDefinition,时AbstractBeanDefinition的子类。
    //所以会进入此代码块
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            //验证beanDefinition,只验证一项内容:beanDefinition不能同时具有overrideMethod和factoryMethod两个属性,如果两个都有,就验证失败
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                                                   "Validation of bean definition failed", ex);
        }
    }
	//创建一个临时变量,用于存放之前同名的beanDefinition(如果有的话)
    BeanDefinition oldBeanDefinition;

    synchronized (this.beanDefinitionMap) {
        //从map中查询是否已经存在名为beanName的beanDefinition
        oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        //如果查到了,就有点意思了。
        //Spring这里有一个标识位allowBeanDefinitionOverriding,表示是否允许BeanDefinition被覆盖。
        //一般这个值都是false,除非特定的业务情境下,允许覆盖同名的BeanDefinition。
        //如果allowBeanDefinitionOverriding是false的话,就抛出异常,表示beanName冲突了
        //如果allowBeanDefinitionOverriding是true的话,就直接覆盖原来的beanDefinition
        if (oldBeanDefinition != null) {
            if (!this.allowBeanDefinitionOverriding) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                                                       "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                                                       "': There is already [" + oldBeanDefinition + "] bound.");
            }
            else {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean '" + beanName +
                                     "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
        }
        else {
            //如果没有同名的BeanDefinition,就把beanName放入到beanDefinitionNames集合中,
         	//这个beanDefinitionNames是一个Set,用于存储所有的BeanDefinition的name,并不是别名,主要是为了加速BeanDefinition的名称检查
            this.beanDefinitionNames.add(beanName);
            this.frozenBeanDefinitionNames = null;
        }
        //将beanDefinition放入到IOC中,这个beanDefinition至此终于纳入了SpringIOC的怀抱
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
	
    //清理之前的oldBeanDefinition(如果有的话)
    //清理单例(如果有的话)
    if (oldBeanDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

Alias在IOC中的注册

IOC是怎么管理alias的

alias是全部放置在一个aliasMap中的进行管理的,别名作为key,真名作为value;允许多个别名对应同一个name。

但是要注意别名循环引用的问题,比如下表:

序号 别名 bean名称
1 alias_1 alias_2
2 alias_2 alias_1

这种引用实际是没有任何意义的,所以Spring需要剔除这些循环引用的别名。

//从readerContext中获取BeanDefinitionRegistry ,来注册别名
//这个BeanDefinitionRegistry也是DefaultLisableBeanFactory,但是DefaultLisableBeanFactory自己没有实现注册别名的方法,而是继承自SimpleAliasRegistry。
getReaderContext().getRegistry().registerAlias(name, alias);

// SimpleAliasRegistry.registerAlias
public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    
    //如果name和别名一样,就不用注册别名了,所以从aliasMap中剔除此别名
    if (alias.equals(name)) {
        this.aliasMap.remove(alias);
    }
    else {
        //查询spring属性allowBeanDefinitionOverriding,是否允许别名被覆盖
        if (!allowAliasOverriding()) {
            //查询此alias是否已经注册了
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null && !registeredName.equals(name)) {
                throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
                                                name + "': It is already registered for name '" + registeredName + "'.");
            }
        }
        //检查别名是否循环引用,见下文
        checkForAliasCircle(name, alias);
        
        //没有循环引用,就放入到aliasMap中,至此一个bean的别名就纳入到SpringIOC管理了。
        this.aliasMap.put(alias, name);
    }
}

checkForAliasCircle 检查别名循环引用

别名的循环引用,会造成死循环并且无法创建出一个有用的Bean,所以Spring一定要避免出现这种情况。

protected void checkForAliasCircle(String name, String alias) {
    //如果alias和规范名称相同,说明别名发生了循环引用,抛出异常就完事了
    if (alias.equals(canonicalName(name))) {
        throw new IllegalStateException("Cannot register alias '" + alias +
                                        "' for name '" + name + "': Circular reference - '" +
                                        name + "' is a direct or indirect alias for '" + alias + "' already");
    }
}
//获取相应的规范名称,如果是别名就取出真正的bean名称
public String canonicalName(String name) {
    String canonicalName = name;
    // Handle aliasing...
    String resolvedName;
    //这个while循环,就是一直在aliasMap中查找,直到获取到真正的beanName,并把这个值存到canonicalName中
    //最后这个canonicalName就是name对应的规范名称
    do {
        resolvedName = this.aliasMap.get(canonicalName);
        if (resolvedName != null) {
            canonicalName = resolvedName;
        }
    }
    while (resolvedName != null);
    return canonicalName;
}

解析流程看下图

查找name真正的名称就是一个“蛇形走位”的过程,通过不断地切换resolvedName和canonicalName地值,在aliasMap中不断地反复横跳,最终找到了真实名称。