Spring解密 - 默认标签的解析

Spring是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是 Java工作中必备技能之一...

前言

紧跟上篇 Spring解密 - XML解析 与 Bean注册 ,我们接着往下分析源码

解密

Spring 的 XML 配置里面有两大类声明,一个是默认的如 <beanid="person"class="com.battcn.bean.Person"/>,另一类就是自定义的如 <tx:annotation-driven/>,两种标签的解析方式差异是非常大的。 parseBeanDefinitions 方法就是用来区分不同标签所使用的解析方式。通过 node.getNamespaceURI() 方法获取命名空间,判断是默认命名空间还是自定义命名空间,并与 Spring 中固定的命名空间 http://www.springframework.org/schema/beans 进行比对,如果一致则采用 parseDefaultElement(ele,delegate);否则就是 delegate.parseCustomElement(ele);

默认标签的解析

parseDefaultElement 对 4 种不同的标签 import、alias、bean、beans 做了不同的处理,其中 bean 标签的解析最为复杂也最为重要,所以我们将从 bean 开始深入分析,如果能理解此标签的解析过程,其他标签的解析自然会迎刃而解。上一篇中只是简单描述了一下,本篇我们围绕解析模块详细的探讨一下

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        // import 标签解析
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        // alias 标签解析
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        // bean 标签解析
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        // import 标签解析
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // beans标签解析 递归方式 
            doRegisterBeanDefinitions(ele);
        }
    }

}

首先我们来分析下当类中的 processBeanDefinition(ele,delegate)

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 解析完成后需要对解析后的bdHolder进行注册,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // 最后发出响应事件,通知相关监听器这个bean已经被加载
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

这段代码中:

  • 首先委托 BeanDefinitionParseDelegate 对节点做了解析,并返回了一个 BeanDefinitionHolder 的实例,在这个实例中已经包含了配置文件中配置的各种属性了
  • 如果在当前子节点中存在自定义属性,则还需要对自定义标签进行解析
  • 解析完成后,需要对解析后的 bdHolder 进行注册,同样注册操作委托给了 BeanDefinitionReaderUtils
  • 最后发出响应事件,通知相关监听器这个 bean 已经被加载

下面我们详细分析下, Spring 是如何解析各个标签和节点的

bean 标签解析

public class BeanDefinitionParserDelegate {

    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        // 获取Bean标签的ID属性
        String id = ele.getAttribute(ID_ATTRIBUTE);
        // 获取Bean标签的Name属性
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        List<String> aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            // 将name属性的值通过,; 进行分割 转为字符串数字(即在配置文件中如配置多个name 在此做处理)
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        String beanName = id;

        // 如果ID为空 使用配置的第一个name属性作为ID
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isDebugEnabled()) {
                logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
            }
        }

        if (containingBean == null) {
            // 校验beanName和aliases的唯一性
            // 内部核心为使用usedNames集合保存所有已经使用了的beanName和alisa
            checkNameUniqueness(beanName, aliases, ele);
        }

        // 进一步解析其他所有属性到GenericBeanDefinition对象中
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

        if (beanDefinition != null) {
            // 如果bean没有指定beanName 那么使用默认规则为此Bean生成beanName
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                    } else {
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // Register an alias for the plain bean class name, if still possible,
                        // if the generator returned the class name plus a suffix.
                        // This is expected for Spring 1.2/2.0 backwards compatibility.
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]");
                    }
                } catch (Exception ex) {
                    error(ex.getMessage(), ele);
                    return null;
                }
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            // 将信息封装到BeanDefinitionHolder对象中
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

}

该方法主要处理了 id、name、alias 等相关属性,生成了 beanName,并且在重载函数 parseBeanDefinitionElement(ele,beanName,containingBean)方法中完成核心的标签解析

接下来重点分析 parseBeanDefinitionElement(Elementele,StringbeanName,@NullableBeanDefinitioncontainingBean) 看下它是如何完成标签解析操作的

bean 节点与属性解析

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, @Nullable BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

    // 获取Bean标签的class属性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    // 获取Bean标签的parent属性
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }

    try {

        // 创建用于承载属性的AbstractBeanDefinition
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // 获取bean标签各种属性
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        // 解析description标签
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        // 解析meta标签
        parseMetaElements(ele, bd);
        // 解析lookup-method标签
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // 解析replaced-method标签
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        // 解析constructor-arg标签
        parseConstructorArgElements(ele, bd);
        // 解析property标签
        parsePropertyElements(ele, bd);
        // 解析qualifier标签
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    }
    catch (ClassNotFoundException ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    }
    catch (NoClassDefFoundError err) {
        error("Class that bean class [" + className + "] depends on not found", ele, err);
    }
    catch (Throwable ex) {
        error("Unexpected failure during bean definition parsing", ele, ex);
    }
    finally {
        this.parseState.pop();
    }

    return null;
}

进一步解析其他属性和元素(元素和属性很多,所以这是一个庞大的工作量)并统一封装至 GenericBeanDefinition 中, 解析完成这些属性和元素之后,如果检测到 bean 没有指定的 beanName,那么便使用默认的规则为 bean 生成一个 beanName

// BeanDefinitionParserDelegate.java
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
        throws ClassNotFoundException {

    return BeanDefinitionReaderUtils.createBeanDefinition(
            parentName, className, this.readerContext.getBeanClassLoader());
}

public class BeanDefinitionReaderUtils {

    public static AbstractBeanDefinition createBeanDefinition(
            @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

        GenericBeanDefinition bd = new GenericBeanDefinition();
        // parentName可能为空
        bd.setParentName(parentName);

        // 如果classLoader不为空 
        // 则使用传入的classLoader同一虚拟机加载类对象 否则只记录classLoader

        if (className != null) {
            if (classLoader != null) {
                bd.setBeanClass(ClassUtils.forName(className, classLoader));
            }
            else {
                bd.setBeanClassName(className);
            }
        }
        return bd;
    }
}

BeanDefinition<bean> 在容器中的内部表示形式, BeanDefinition<bean> 是一一对应的。同时 BeanDefinition 会被注册到 BeanDefinitionRegistry 中, BeanDefinitionRegistry 就像 Spring 配置信息的内存数据库。

至此 createBeanDefinition(className,parent); 已经说完了,而且我们也获得了 用于承载属性的AbstractBeanDefinition,接下来看看 parseBeanDefinitionAttributes(ele,beanName,containingBean,bd); 是如何解析 bean 中的各种标签属性的

public class BeanDefinitionParserDelegate {

    public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
            @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

        // ...省略详细代码,该部分代码主要就是通过 if else 判断是否含有指定的属性,如果有就 bd.set(attribute);

        return bd;
    }

}

bean 标签的完整解析到这就已经全部结束了,其中 bean 标签下的元素解析都大同小异,有兴趣的可以自己跟踪一下源代码看看 qualifier、lookup-method 等解析方式(相对 bean 而言不复杂)。自定义标签内容较多会在下一章详细介绍。

最后将获取到的信息封装到 BeanDefinitionHolder 实例中

// BeanDefinitionParserDelegate.java
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // ...
    return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}

注册解析的 BeanDefinition

在解析完配置文件后我们已经获取了 bean 的所有属性,接下来就是对 bean 的注册了

public class BeanDefinitionReaderUtils {

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // 使用 beanName 做唯一标识符
        String beanName = definitionHolder.getBeanName();

        // 注册bean的核心代码
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // 为bean注册所有的别名
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

}

以上代码主要完成两个功能,一是使用 beanName 注册 beanDefinition,二是完成了对别名的注册

BeanName 注册 BeanDefinition

public class DefaultListableBeanFactory {

    @Override
    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 {

                // 注册前的最后一次校验,这里的校验不同于XML文件校验
                // 主要是对于AbstractBeanDefinition属性中的methodOverrides校验
                // 校验methodOverrides是否与工厂方法并存或者methodOverrides对于的方法根本不存在
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        BeanDefinition oldBeanDefinition;
        // 获取缓存中的 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 + "]");
                }
            }
            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到beanDefinitionMap中
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            // 判断是否已经开始创建bean
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {

                    // 保存beanDefinition到beanDefinitionMap中
                    this.beanDefinitionMap.put(beanName, beanDefinition);

                    // 更新已经注册的beanName
                    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 {
                // 还没开始创建bean
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (oldBeanDefinition != null || containsSingleton(beanName)) {
            // 重置beanName对应的缓存
            resetBeanDefinition(beanName);
        }
    }

}
  • AbstractBeanDefinition 的校验,主要是针对 AbstractBeanDefinitionmethodOverrides 属性的
  • beanName 已经注册的情况的处理,如果设置了不允许 bean 的覆盖,则需要抛出异常,否则直接覆盖
  • 使用 beanName 作为 key, beanDefinition 为 Value 加入 beanDefinitionMap 存储
  • 如果缓存中已经存在,并且该 bean 为单例模式则清楚 beanName 对应的缓存

注册别名

注册好了 beanDefinition,接下来就是注册 alias。注册的 aliasbeanName 的对应关系存放在了 aliasMap中,沿着类的继承链会发现 registerAlias 的方法是在 SimpleAliasRegistry 中实现的

public class SimpleAliasRegistry {

    /** Map from alias to canonical name */
    private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);

    public void registerAlias(String name, String alias) {
        Assert.hasText(name, "'name' must not be empty");
        Assert.hasText(alias, "'alias' must not be empty");
        if (alias.equals(name)) {
            // 如果beanName与alias相同的话不记录alias 并删除对应的alias
            this.aliasMap.remove(alias);
        } else {
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    // 如果别名已经注册过并且指向的name和当前name相同 不做任何处理
                    return;
                }
                // 如果alias不允许被覆盖则抛出异常
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
                }
            }
            // 校验循环指向依赖 如A->B B->C C->A则出错
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
        }
    }

}

通过 checkForAliasCircle() 方法来检查 alias 循环依赖,当 A -> B 存在时,若再次出现 A -> C -> B 则会抛出异常:

protected void checkForAliasCircle(String name, String alias) {
    if (hasAlias(alias, name)) {
        throw new IllegalStateException("Cannot register alias '" + alias +
                "' for name '" + name + "': Circular reference - '" +
                name + "' is a direct or indirect alias for '" + alias + "' already");
    }
}

public boolean hasAlias(String name, String alias) {
    for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
        String registeredName = entry.getValue();
        if (registeredName.equals(name)) {
            String registeredAlias = entry.getKey();
            return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias));
        }
    }
    return false;
}

至此,注册别名也完成了,主要完成了以下几个工作

  • 如果 beanNamealias 相同的话不记录 alias 并删除对应的 alias
  • 如果别名已经注册过并且指向的name和当前name相同 不做任何处理
  • 如果别名已经注册过并且指向的name和当前name不相同 判断是否允许被覆盖
  • 校验循环指向依赖 如A->B B->C C->A则出错

发送通知

通知监听器解析及注册完成

//DefaultBeanDefinitionDocumentReader.java
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // Send registration event.
    getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}

通过 fireComponentRegistered 方法进行通知监听器解析及注册完成工作,这里的实现只为扩展,当程序开发人员需要对注册 BeanDefinition事件进行监听时,可以通过注册监听器的方式并将处理逻辑写入监听器中,目前 Spring 中并没有对此事件做任何处理

其中 ReaderContext 是在类 XmlBeanDefinitionReader 中调用 createReaderContext 生成的,然后调用 fireComponentRegistered()

alias 标签解析

Spring 提供了 <aliasname="person"alias="p"/> 方式来进行别名的配置,该标签解析是在 processAliasRegistration(Elementele) 方法中完成的

public class DefaultBeanDefinitionDocumentReader {

    protected void processAliasRegistration(Element ele) {
        // 获取 alisa 标签 name 属性
        String name = ele.getAttribute(NAME_ATTRIBUTE);
        // 获取 alisa 标签 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 {
                // 进行别名注册
                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));
        }
    }

}

首先对 alias 标签属性进行提取校验,校验通过后进行别名注册,别名注册和 bean 标签解析中的别名注册一直,此处不再赘述

import 标签解析

public class DefaultBeanDefinitionDocumentReader {

    protected void importBeanDefinitionResource(Element ele) {
        // 获取import标签的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);

        // 判断资源是绝对路径还是相对路径
        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[actualResources.size()]);
        getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
    }

}

完成了对 import 标签的处理,首先就是获取 <importresource="beans.xml"/> resource 属性所表示的路径,接着解析路径中的属性占位符 如 ${user.dir},然后判定 location 是绝对路径还是相对路径,如果是绝对路径则递归调用 bean 的解析过程( loadBeanDefinitions(location,actualResources);),进行另一次解析,如果是相对路径则计算出绝对路径并进行解析,最后通知监听器,解析完成

总结

熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走...

说点什么

全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1

原文发布于微信公众号 - battcn(battcn)

原文发表时间:2018-01-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang context 包入门

概述 Golang 的 context Package 提供了一种简洁又强大方式来管理 goroutine 的生命周期,同时提供了一种 Requst-Scope...

393100
来自专栏技术小黑屋

如何在Android中避免创建不必要的对象

在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用。这其中避免创建不必要的对象是一项重要的方面。

8120
来自专栏程序猿DD

Spring框架中的设计模式(五)

通过以前的4篇文章,我们看到Spring采用了大量的关于创建和结构方面的设计模式。本文将描述属于行为方面的两种设计模式:命令和访问者。 前传: Spring框架...

50670
来自专栏Android Note

RecyclerView 刷新闪烁

45870
来自专栏青蛙要fly的专栏

项目需求讨论-Vlayout来快速构建及扩展复杂界面

大家好,今天又带来了项目中具体遇到的需求。做一个首界面,该首界面有很多功能块,同时这些功能块是动态的,因为登录的人的权限的不同,会显示不同的功能块,因为功能模块...

26920
来自专栏移动开发的那些事儿

内存泄露的一些坑

如上,在Activity内部如果声明一个这样的Handler,那么myHandler就默认持有Activity引用,假设Activity退出了,但是可能这时候才...

30520
来自专栏Ryan Miao

配置springmvc在其他类中(spring容器外)获取注入bean

学习https://github.com/thinkgem/jeesite 今天在写JedisUtils的时候要注入JedisPool,而这个属性被设置为sta...

30350
来自专栏分享达人秀

使用SimpleAdapter

通过ArrayAdapter实现Adapter虽然简单、易用,但ArrayAdapter的功能比较有限,它的每个列表项只能给一个TextView动态填充...

212100
来自专栏向治洪

[置顶] 浅谈我为什么选择用Retrofit作为我的网络请求框架

比较AsyncTask、Volley、Retrofit三者的请求时间 使用 单次请求 7个请求 25个请求 AsyncTask 94...

23550
来自专栏Java与Android技术栈

基于Kotlin的委托机制实现一个对Extra、SharedPreferences操作的库

本文介绍的库,github地址:https://github.com/fengzhizi715/SAF-Object-Delegate

16530

扫码关注云+社区

领取腾讯云代金券