欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring源码深度解析— IOC 之 默认标签解析(下)

程序员文章站 2022-05-18 15:42:03
在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例。本文主要来看看标签解析剩余部分及BeanDefinition的注册。 默认标签中的自定义标签解析 在上篇博 ......

spring源码深度解析— ioc 之 默认标签解析(上)中我们已经完成了从xml配置文件到beandefinition的转换,转换后的实例是genericbeandefinition的实例。本文主要来看看标签解析剩余部分及beandefinition的注册。

默认标签中的自定义标签解析

在上篇博文中我们已经分析了对于默认标签的解析,我们继续看戏之前的代码,如下图片中有一个方法:delegate.decoratebeandefinitionifrequired(ele, bdholder) 

spring源码深度解析— IOC 之 默认标签解析(下)

这个方法的作用是什么呢?首先我们看下这种场景,如下配置文件:

 <bean id="demo" class="com.chenhao.spring.mytestbean">
     <property name="beanname" value="bean demo1"/>
     <meta key="demo" value="demo"/>
     <mybean:username="mybean"/>
 </bean>

这个配置文件中有个自定义的标签,decoratebeandefinitionifrequired方法就是用来处理这种情况的,其中的null是用来传递父级beandefinition的,我们进入到其方法体:

public beandefinitionholder decoratebeandefinitionifrequired(element ele, beandefinitionholder definitionholder) {
    return decoratebeandefinitionifrequired(ele, definitionholder, null);
}
public beandefinitionholder decoratebeandefinitionifrequired(
        element ele, beandefinitionholder definitionholder, @nullable beandefinition containingbd) {

    beandefinitionholder finaldefinition = definitionholder;

    // decorate based on custom attributes first.
    namednodemap attributes = ele.getattributes();
    for (int i = 0; i < attributes.getlength(); i++) {
        node node = attributes.item(i);
        finaldefinition = decorateifrequired(node, finaldefinition, containingbd);
    }

    // decorate based on custom nested elements.
    nodelist children = ele.getchildnodes();
    for (int i = 0; i < children.getlength(); i++) {
        node node = children.item(i);
        if (node.getnodetype() == node.element_node) {
            finaldefinition = decorateifrequired(node, finaldefinition, containingbd);
        }
    }
    return finaldefinition;
}

我们看到上面的代码有两个遍历操作,一个是用于对所有的属性进行遍历处理,另一个是对所有的子节点进行处理,两个遍历操作都用到了decorateifrequired(node, finaldefinition, containingbd);方法,我们继续跟踪代码,进入方法体:

public beandefinitionholder decorateifrequired(
        node node, beandefinitionholder originaldef, @nullable beandefinition containingbd) {
    // 获取自定义标签的命名空间
    string namespaceuri = getnamespaceuri(node);
    // 过滤掉默认命名标签
    if (namespaceuri != null && !isdefaultnamespace(namespaceuri)) {
        // 获取相应的处理器
        namespacehandler handler = this.readercontext.getnamespacehandlerresolver().resolve(namespaceuri);
        if (handler != null) {
            // 进行装饰处理
            beandefinitionholder decorated =
                    handler.decorate(node, originaldef, new parsercontext(this.readercontext, this, containingbd));
            if (decorated != null) {
                return decorated;
            }
        }
        else if (namespaceuri.startswith("http://www.springframework.org/")) {
            error("unable to locate spring namespacehandler for xml schema namespace [" + namespaceuri + "]", node);
        }
        else {
            if (logger.isdebugenabled()) {
                logger.debug("no spring namespacehandler found for xml schema namespace [" + namespaceuri + "]");
            }
        }
    }
    return originaldef;
}

public string getnamespaceuri(node node) {
    return node.getnamespaceuri();
}

public boolean isdefaultnamespace(@nullable string namespaceuri) {
    //beans_namespace_uri = "http://www.springframework.org/schema/beans";
    return (!stringutils.haslength(namespaceuri) || beans_namespace_uri.equals(namespaceuri));
}

首先获取自定义标签的命名空间,如果不是默认的命名空间则根据该命名空间获取相应的处理器,最后调用处理器的 decorate() 进行装饰处理。具体的装饰过程这里不进行讲述,在后面分析自定义标签时会做详细说明。

注册解析的beandefinition

对于配置文件,解析和装饰完成之后,对于得到的beandefinition已经可以满足后续的使用要求了,还剩下注册,也就是processbeandefinition函数中的beandefinitionreaderutils.registerbeandefinition(bdholder,getreadercontext().getregistry())代码的解析了。进入方法体:

public static void registerbeandefinition(
        beandefinitionholder definitionholder, beandefinitionregistry registry)
        throws beandefinitionstoreexception {
    // register bean definition under primary name.
    //使用beanname做唯一标识注册
    string beanname = definitionholder.getbeanname();
    registry.registerbeandefinition(beanname, definitionholder.getbeandefinition());

    // register aliases for bean name, if any.
    //注册所有的别名
    string[] aliases = definitionholder.getaliases();
    if (aliases != null) {
        for (string alias : aliases) {
            registry.registeralias(beanname, alias);
        }
    }
}

从上面的代码我们看到是用了beanname作为唯一标示进行注册的,然后注册了所有的别名aliase。而beandefinition最终都是注册到beandefinitionregistry中,接下来我们具体看下注册流程。

通过beanname注册beandefinition

在spring中除了使用beanname作为key将beandefinition放入map中还做了其他一些事情,我们看下方法registerbeandefinition代码,beandefinitionregistry是一个接口,他有三个实现类,defaultlistablebeanfactory、simplebeandefinitionregistry、genericapplicationcontext,其中simplebeandefinitionregistry非常简单,而genericapplicationcontext最终也是使用的defaultlistablebeanfactory中的实现方法,我们看下代码:

public void registerbeandefinition(string beanname, beandefinition beandefinition)
        throws beandefinitionstoreexception {

    // 校验 beanname 与 beandefinition
    assert.hastext(beanname, "bean name must not be empty");
    assert.notnull(beandefinition, "beandefinition must not be null");

    if (beandefinition instanceof abstractbeandefinition) {
        try {
            // 校验 beandefinition
            // 这是注册前的最后一次校验了,主要是对属性 methodoverrides 进行校验
            ((abstractbeandefinition) beandefinition).validate();
        }
        catch (beandefinitionvalidationexception ex) {
            throw new beandefinitionstoreexception(beandefinition.getresourcedescription(), beanname,
                    "validation of bean definition failed", ex);
        }
    }

    beandefinition oldbeandefinition;

    // 从缓存中获取指定 beanname 的 beandefinition
    oldbeandefinition = this.beandefinitionmap.get(beanname);
    /**
     * 如果存在
     */
    if (oldbeandefinition != null) {
        // 如果存在但是不允许覆盖,抛出异常
        if (!isallowbeandefinitionoverriding()) {
            throw new beandefinitionstoreexception(beandefinition.getresourcedescription(), beanname,
                    "cannot register bean definition [" + beandefinition + "] for bean '" + beanname +
                            "': there is already [" + oldbeandefinition + "] bound.");
        }
        //
        else if (oldbeandefinition.getrole() < beandefinition.getrole()) {
            // e.g. was role_application, now overriding with role_support or role_infrastructure
            if (this.logger.iswarnenabled()) {
                this.logger.warn("overriding user-defined bean definition for bean '" + beanname +
                        "' with a framework-generated bean definition: replacing [" +
                        oldbeandefinition + "] with [" + beandefinition + "]");
            }
        }
        // 覆盖 beandefinition 与 被覆盖的 beandefinition 不是同类
        else if (!beandefinition.equals(oldbeandefinition)) {
            if (this.logger.isinfoenabled()) {
                this.logger.info("overriding bean definition for bean '" + beanname +
                        "' with a different definition: replacing [" + oldbeandefinition +
                        "] with [" + beandefinition + "]");
            }
        }
        else {
            if (this.logger.isdebugenabled()) {
                this.logger.debug("overriding bean definition for bean '" + beanname +
                        "' with an equivalent definition: replacing [" + oldbeandefinition +
                        "] with [" + beandefinition + "]");
            }
        }

        // 允许覆盖,直接覆盖原有的 beandefinition
        this.beandefinitionmap.put(beanname, beandefinition);
    }
    /**
     * 不存在
     */
    else {
         // 检测创建 bean 阶段是否已经开启,如果开启了则需要对 beandefinitionmap 进行并发控制
        if (hasbeancreationstarted()) {
            // beandefinitionmap 为全局变量,避免并发情况
            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;
                if (this.manualsingletonnames.contains(beanname)) {
                    set<string> updatedsingletons = new linkedhashset<>(this.manualsingletonnames);
                    updatedsingletons.remove(beanname);
                    this.manualsingletonnames = updatedsingletons;
                }
            }
        }
        else {
            // 不会存在并发情况,直接设置
            this.beandefinitionmap.put(beanname, beandefinition);
            this.beandefinitionnames.add(beanname);
            this.manualsingletonnames.remove(beanname);
        }
        this.frozenbeandefinitionnames = null;
    }

    if (oldbeandefinition != null || containssingleton(beanname)) {
        // 重新设置 beanname 对应的缓存
        resetbeandefinition(beanname);
    }
}

处理过程如下:

  • 首先 beandefinition 进行校验,该校验也是注册过程中的最后一次校验了,主要是对 abstractbeandefinition 的 methodoverrides 属性进行校验
  • 根据 beanname 从缓存中获取 beandefinition,如果缓存中存在,则根据 allowbeandefinitionoverriding 标志来判断是否允许覆盖,如果允许则直接覆盖,否则抛出 beandefinitionstoreexception 异常
  • 若缓存中没有指定 beanname 的 beandefinition,则判断当前阶段是否已经开始了 bean 的创建阶段(),如果是,则需要对 beandefinitionmap 进行加锁控制并发问题,否则直接设置即可。对于 hasbeancreationstarted() 方法后续做详细介绍,这里不过多阐述。
  • 若缓存中存在该 beanname 或者 单利 bean 集合中存在该 beanname,则调用 resetbeandefinition() 重置 beandefinition 缓存。

其实整段代码的核心就在于 this.beandefinitionmap.put(beanname, beandefinition); 。beandefinition 的缓存也不是神奇的东西,就是定义 一个 concurrenthashmap,key 为 beanname,value 为 beandefinition。

通过别名注册beandefinition

通过别名注册beandefinition最终是在simplebeandefinitionregistry中实现的,我们看下代码:

public void registeralias(string name, string alias) {
    assert.hastext(name, "'name' must not be empty");
    assert.hastext(alias, "'alias' must not be empty");
    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)) {
                    // an existing alias - no need to re-register
                    return;
                }
                if (!allowaliasoverriding()) {
                    throw new illegalstateexception("cannot register alias '" + alias + "' for name '" +
                            name + "': it is already registered for name '" + registeredname + "'.");
                }
            }
            //当a->b存在时,若再次出现a->c->b时候则会抛出异常。
            checkforaliascircle(name, alias);
            this.aliasmap.put(alias, name);
        }
    }
}

上述代码的流程总结如下:
(1)alias与beanname相同情况处理,若alias与beanname并名称相同则不需要处理并删除原有的alias
(2)alias覆盖处理。若aliasname已经使用并已经指向了另一beanname则需要用户的设置进行处理
(3)alias循环检查,当a->b存在时,若再次出现a->c->b时候则会抛出异常。

alias标签的解析

对应bean标签的解析是最核心的功能,对于alias、import、beans标签的解析都是基于bean标签解析的,接下来我就分析下alias标签的解析。我们回到 parsedefaultelement(element ele, beandefinitionparserdelegate delegate)方法,继续看下方法体,如下图所示:

spring源码深度解析— IOC 之 默认标签解析(下)

对bean进行定义时,除了用id来 指定名称外,为了提供多个名称,可以使用alias标签来指定。而所有这些名称都指向同一个bean。在xml配置文件中,可用单独的元素来完成bean别名的定义。我们可以直接使用bean标签中的name属性,如下:

<bean id="demo" class="com.chenhao.spring.mytestbean" name="demo1,demo2">
    <property name="beanname" value="bean demo1"/>
</bean>

在spring还有另外一种声明别名的方式:

<bean id="mytestbean" class="com.chenhao.spring.mytestbean"/>
<alias name="mytestbean" alias="testbean1,testbean2"/>

我们具体看下alias标签的解析过程,解析使用的方法processaliasregistration(ele),方法体如下:

protected void processaliasregistration(element ele) {
    //获取beanname
    string name = ele.getattribute(name_attribute);
    //获取alias
    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 {
            //注册alias
            getreadercontext().getregistry().registeralias(name, alias);
        }
        catch (exception ex) {
            getreadercontext().error("failed to register alias '" + alias +
                    "' for bean with name '" + name + "'", ele, ex);
        }
        getreadercontext().firealiasregistered(name, alias, extractsource(ele));
    }
}

通过代码可以发现解析流程与bean中的alias解析大同小异,都是讲beanname与别名alias组成一对注册到registry中。跟踪代码最终使用了simplealiasregistry中的registeralias(string name, string alias)方法

推荐博客

  

import标签的解析

对于spring配置文件的编写,经历过大型项目的人都知道,里面有太多的配置文件了。基本采用的方式都是分模块,分模块的方式很多,使用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">

    <bean id="demo" class="com.chenhao.spring.mytestbean" name="demo1,demo2">
        <property name="beanname" value="bean demo1"/>
    </bean>
    <import resource="lookup-method.xml"/>
    <import resource="replaced-method.xml"/>
</beans>

applicationcontext.xml文件中使用import方式导入有模块配置文件,以后若有新模块的加入,那就可以简单修改这个文件了。这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理。我们来看看spring是如何解析import配置文件的呢。解析import标签使用的是importbeandefinitionresource(ele),进入方法体:

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;
    }

    // 解析系统属性,格式如 :"${user.dir}"
    location = getreadercontext().getenvironment().resolverequiredplaceholders(location);

    set<resource> actualresources = new linkedhashset<>(4);

    // 判断 location 是相对路径还是绝对路径
    boolean absolutelocation = false;
    try {
        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 {
            // 直接根据地址加载相应的配置文件
            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 {
                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);
        }
    }
    // 解析成功后,进行监听器激活处理
    resource[] actresarray = actualresources.toarray(new resource[0]);
    getreadercontext().fireimportprocessed(location, actresarray, extractsource(ele));
}

解析 import 过程较为清晰,整个过程如下:

  1. 获取 source 属性的值,该值表示资源的路径
  2. 解析路径中的系统属性,如”${user.dir}”
  3. 判断资源路径 location 是绝对路径还是相对路径
  4. 如果是绝对路径,则调递归调用 bean 的解析过程,进行另一次的解析
  5. 如果是相对路径,则先计算出绝对路径得到 resource,然后进行解析
  6. 通知监听器,完成解析

判断路径

方法通过以下方法来判断 location 是为相对路径还是绝对路径:

absolutelocation = resourcepatternutils.isurl(location) || resourceutils.touri(location).isabsolute();

判断绝对路径的规则如下:

  • 以 classpath*: 或者 classpath: 开头为绝对路径
  • 能够通过该 location 构建出 java.net.url为绝对路径
  • 根据 location 构造 java.net.uri 判断调用 isabsolute() 判断是否为绝对路径

如果 location 为绝对路径则调用 loadbeandefinitions(),该方法在 abstractbeandefinitionreader 中定义。

public int loadbeandefinitions(string location, @nullable set<resource> actualresources) throws beandefinitionstoreexception {
    resourceloader resourceloader = getresourceloader();
    if (resourceloader == null) {
        throw new beandefinitionstoreexception(
                "cannot import bean definitions from location [" + location + "]: no resourceloader available");
    }

    if (resourceloader instanceof resourcepatternresolver) {
        // resource pattern matching available.
        try {
            resource[] resources = ((resourcepatternresolver) resourceloader).getresources(location);
            int loadcount = loadbeandefinitions(resources);
            if (actualresources != null) {
                for (resource resource : resources) {
                    actualresources.add(resource);
                }
            }
            if (logger.isdebugenabled()) {
                logger.debug("loaded " + loadcount + " bean definitions from location pattern [" + location + "]");
            }
            return loadcount;
        }
        catch (ioexception ex) {
            throw new beandefinitionstoreexception(
                    "could not resolve bean definition resource pattern [" + location + "]", ex);
        }
    }
    else {
        // can only load single resources by absolute url.
        resource resource = resourceloader.getresource(location);
        int loadcount = loadbeandefinitions(resource);
        if (actualresources != null) {
            actualresources.add(resource);
        }
        if (logger.isdebugenabled()) {
            logger.debug("loaded " + loadcount + " bean definitions from location [" + location + "]");
        }
        return loadcount;
    }
}

整个逻辑比较简单,首先获取 resourceloader,然后根据不同的 resourceloader 执行不同的逻辑,主要是可能存在多个 resource,但是最终都会回归到 xmlbeandefinitionreader.loadbeandefinitions() ,所以这是一个递归的过程。

至此,import 标签解析完毕,整个过程比较清晰明了:获取 source 属性值,得到正确的资源路径,然后调用 loadbeandefinitions() 方法进行递归的 beandefinition 加载。