注释

在上一篇咱们曾经实现了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例。本文次要来看看标签解析残余局部及BeanDefinition的注册。

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

在上篇博文中咱们曾经剖析了对于默认标签的解析,咱们持续看戏之前的代码,如下图片中有一个办法:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)

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

 <bean id="demo" class="com.dabin.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)办法,持续看下办法体,如下图所示:

对bean进行定义时,除了用id来 指定名称外,为了提供多个名称,能够应用alias标签来指定。而所有这些名称都指向同一个bean。在XML配置文件中,可用独自的元素来实现bean别名的定义。咱们能够间接应用bean标签中的name属性,如下:

<bean id="demo" class="com.dabin.spring.MyTestBean" name="demo1,demo2">    <property name="beanName" value="bean demo1"/></bean>

在Spring还有另外一种申明别名的形式:

<bean id="myTestBean" class="com.dabin.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.dabin.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 加载。