Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-AbstractApplicationContext#obtainFreshBeanFactory

前文提到最关键的地方是解析bean xml配置文件,废话不多说,直接上代码清单

//正如官方注释所说,解析import标签、alias标签、bean标签和自定义的标签
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //检查<beans>根标签的命名空间是否为空或者是http://www.springframework.org/schema/beans
        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)) 
                        //解析import标签、alias标签、bean标签、内置<beans>标签
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //解析自定义标签,例如<context:component-scan basePackage="" />
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

那我们根据两个部分来解读对bean xml文件的解析,分别为DefaultBeanDefinitionDocumentReader#parseDefaultElementBeanDefinitionParserDelegate#parseCustomElement

DefaultBeanDefinitionDocumentReader#parseDefaultElement

解析import标签、alias标签、bean标签,代码清单如下

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);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse 递归
            doRegisterBeanDefinitions(ele);
        }
    }

三种解析具体方法
1.DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource
import标签解析代码比较长,逻辑倒是很简单,代码清单如下

    protected void importBeanDefinitionResource(Element ele) {
        //获取<import>标签的resource属性值
        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        //resource不允许为空
        if (!StringUtils.hasText(location)) {
            getReaderContext().error("Resource location must not be empty", ele);
            return;
        }

        // Resolve system properties: e.g. "${user.dir}"
        //这里强调下,对于<import>标签,其会从System.getProperties()和System.getenv()中属性获取,优先System.getProperties()
        //即优先系统变量,环境变量次之
        location = environment.resolveRequiredPlaceholders(location);

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

        // Discover whether the location is an absolute or relative URI
        //resource属性支持URL模式,找到相应的资源并进行加载
        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*:"
        }

        // Absolute or relative?
        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 {
            // No URL -> considering resource location as relative to the current file.
            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));
    }

2.DefaultBeanDefinitionDocumentReader#processAliasRegistration
主要目的就是缓存全局别名

    protected void processAliasRegistration(Element ele) {
        //这里确保<alias>标签的name、alias值不为空 e.g. <alias name="key" alias="value">
        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 {
                //全局设置,查阅后其会保存到DefaultListableFactory的父级类`SimpleAliasRegistry#aliasMap`中
                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));
        }
    }

3.DefaultBeanDefinitionDocumentReader#processBeanDefinition
<bean>标签的解析处理

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //初始化bean标签的相应内容
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            //对bean标签中的属性和子标签,进行相应的具体解析,例如property、constructor-args
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            //目前尚无实现
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }
BeanDefinitionParserDelegate#parseBeanDefinitionElement

解析bean标签,也许会返回null在出现解析错误的时候,代码清单如下

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        //获取<bean>的 id属性
        String id = ele.getAttribute(ID_ATTRIBUTE);
        //如果有name属性,则获取name,并且可有多个name,以,;为分隔符
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        List<String> aliases = new ArrayList<String>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        String beanName = id;
        //当id属性不存在且name属性有值时,默认使用第一个name值
        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");
            }
        }
        //确保beanName的唯一性,即在应用使用前不允许有两个的beanName一致
        if (containingBean == null) {
            checkNameUniqueness(beanName, aliases, ele);
        }
        //解析除id、name属性的情况
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            //省略部分代码
            ...
            ...
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

限于篇幅太长,其余的情况我们就不讲解了,有兴趣的可以自行前往,但在此根据读到的源码对bean解析进行总结

  1. parent属性代表父类,这里表示的是父类的唯一id名

  2. 不允许有singleton属性;可有scope属性;
    可有abstract属性,值可为true/false;
    可有lazyinit属性,值为default则沿用beans标签中的lazyinit,否则为true/false;
    可有autowire属性,值为default则沿用beans标签的autowirebyName表示按照名称注入、byType按照类型注入、constructor表明按照构造函数注入、autodetect表明自动检测(官方不建议使用)、默认则不使用注入;
    可有dependency-check属性,值有allobjectssimple,默认为不检测;
    可有depends-on属性,可有多值,以,;为分隔符;
    可有autowire-candidate属性,值可为true/false/default/空字符串
    可有primary属性,值为true/false;
    可有init-method属性,初始化方法,对应可有destroy-method属性;
    可有facroty-method属性,工厂方法;
    可有factory-bean属性

  3. bean标签下可有description标签,表示对此bean的描述;
    也可有meta标签;
    可有lookup-method标签;
    可有replaced-method标签;
    可有constructor-arg标签;
    可有property标签,其中valueref属性只可选用一个;
    可有qualifier标签,其下可有attribute标签,同@Qualifier注解
  4. 所有的bean信息都由BeanDefinitionHolder对象来保存,其中的BeanDefinition包含了一个bean标签的所有可能内容

BeanDefinitionParserDelegate#parseCustomElement

解析自定义的标签节点,例如<context:component-scan><tx:annotation-driven>等,简单看下代码清单

    //containingBd此处为null
    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        //获取命名空间
        String namespaceUri = getNamespaceURI(ele);
        //从map集合中获取NamespaceHandler接口
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        //调用统一的解析接口
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

按照上述的注释,我们需要查看下namespaceURI对应的NamespaceHandler到底是如何获取的,真实调用的是DefaultNamespaceHandlerResolver类,稍微分析下此类


构造函数

    public DefaultNamespaceHandlerResolver() {
        //第二个参数值为 META-INF/spring.handlers
        this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }
    
    //此处所用的是handlerMappingLocation,指的是handler接口类加载的文件资源
    public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
        Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
        this.handlerMappingsLocation = handlerMappingsLocation;
    }

resolve解析方法

        public NamespaceHandler resolve(String namespaceUri) {
        //调用的是PropertiesLoaderUtils.loadAllProperties读取所有classpath下的'META-INF/spring.handlers'文件,保存至Map集合
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            String className = (String) handlerOrClassName;
            try {
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                //声明的handler必须是NamespaceHandler的实现类
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                //实例化Handler类
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                //初始化
                namespaceHandler.init();
                //缓存下
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }

限于篇幅太长,关于bean解析以及custom的具体解析未来会分条详细介绍,敬请期待

下节预告

Spring源码情操陶冶-AbstractApplicationContext#prepareBeanFactory

转载于:https://www.cnblogs.com/question-sky/p/6706362.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值