17--Spring解析xml默认命名空间

上一节分析了Spring将xml文件转换为Document对象的过程,接下来Spring会对已转换的Document对象进行解析。打开XmlBeanDefinitionReader类的registerBeanDefinitions方法。打开DefaultBeanDefinitionDocumentReader类的doRegisterBeanDefinitions方法。

1. 引言
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 1、创建documentReader对象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 2、读取已经注册的bean个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 3、解析Document并注册BeanDefinition
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 4、返回本次注册的bean个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

该方法总共分为了四步,第三步是执行对Document对象解析和注册BeanDefinition。先来看对Document对象的解析过程。

2.doRegisterBeanDefinitions方法分析
protected void doRegisterBeanDefinitions(Element root) {
    // 1、创建BeanDefinitionParserDelegate对象,用来解析Element元素
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    // 2、解析并验证profile节点,如果配置了profile属性,则验证当前环境是否激活了对应的profile节点,
    // 用于多开发环境配置,该方式在开发中已不多见。
    // 例如:System.setProperty("spring.profiles.active", "dev");
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
                    BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }

    // 3、解析前置处理,空的模板方法
    preProcessXml(root);

    // 4、解析并注册BeanDefinition
    parseBeanDefinitions(root, this.delegate);

    // 5、解析后置处理,空的模板方法
    postProcessXml(root);

    this.delegate = parent;
}

doRegisterBeanDefinitions方法并没有自己去执行对xml节点的解析而是委托给了BeanDefinitionParserDelegate,通过代理去解析xml节点。Spring的xml文件又可能包含默认命名空间和自定义命名空间,我们来看Spring是如何进行处理的。

3.parseBeanDefinitions方法简析
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 1、解析默认命名空间
    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)) {
                    parseDefaultElement(ele, delegate);
                }
                // 自定义命名空间
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    // 2、解析自定义命名空间
    else {
        delegate.parseCustomElement(root);
    }
}

这与上一节的xml解析分析大致相同,只是增加了对命名空间的判断,针对不同的命名空间使用不同的方法进行解析。先来看对默认命名空间中的标签的解析。

4.parseDefaultElement解析默认标签
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 1、解析import标签,并注册beanDefinition
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // 2、解析alias标签,并注册别名
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // 3、解析bean标签,并注册beanDefinition
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // 4、解析beans标签
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        doRegisterBeanDefinitions(ele);
    }
}

代码到这里,相信大家已经有所了然了,日常开发中的常见标签已经展现出来了(虽然现在提倡无配置文件,但是还是要对其原理有所了解)。parseDefaultElement方法对import、alias、bean、beans四种标签分别做了单独处理,bean标签的解析最复杂也是一个比较基础的操作,先来看对bean标签的解析,如果大家能够了解bean的解析,那么其他的解析过程相信大家都能看懂,因为相对而言要比解析bean标签简单的多。

5.解析Bean标签
/**
 * 解析bean标签将其转换为definition并注册到BeanDefinitionRegistry
 * Process the given bean element, parsing the bean definition and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 1、将解析的节点信息封装至BeanDefinitionHolder对象
    // BeanDefinitionHolder-->封装了BeanDefinition,beanName以及aliases
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 2、装饰BeanDefinition
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            // 3、执行注册
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        // 4、发送注册事件
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

可以看到Spring将解析之后的内容封装至了BeanDefinitionHolder类,该类封装了BeanDefinition,beanName以及aliases,BeanDefinition封装了bean标签的各种信息。

该方法还执行了对BeanDefinition的注册,留在下节分析。来看bean标签解析的具体过程。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 1、解析bean的id和bean的别名
    // 获取id标签属性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // 获取name(别名)标签属性,缓存至List集合
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    List<String> aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }

    // 如果bean的id为空,但是别名不为空的话,那么默认采用第一个别名作为beanName
    // 例如:<bean class="com.lyc.cn.v2.day01.Dog" name="myDog1,myDog2"/>,使用myDog1作为beanName
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
    }

    // 2、containingBean不为空,则检查beanName和别名是否被使用
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }

    // 3、解析bean标签,将其转换为BeanDefinition对象
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

    // 4、如果beanDefinition且未配置bean的id属性,name属性,则为当前bean生成id和别名
    // 例如:<bean class="com.lyc.cn.v2.day01.Dog"/>,<bean factory-bean="dog4"/>,<bean parent="outer"/>等
    if (beanDefinition != null) {
        if (!StringUtils.hasText(beanName)) {
            try {
                // containingBean不为null,则当前bean是内部bean,使用BeanDefinitionReaderUtils生成beanName
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                    // 否则,使用XmlReaderContext对象生成beanName
                    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.
                    // 如果生成器返回的是类名加上后缀,则为普通bean类名注册一个别名。这在Spring 1.2/2.0的向后兼容性中是可以实现的。
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null
                            && beanName.startsWith(beanClassName)
                            && beanName.length() > beanClassName.length()
                            && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
            } catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }
        // 5、创建并返回BeanDefinitionHolder对象
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }
    return null;
}

首先对id,name属性进行解析,该过程比较简单,接下来就是对bean中其他属性的解析。来看parseBeanDefinitionElement方法。

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    this.parseState.push(new BeanEntry(beanName));
    // 1、解析class属性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    // 2、解析parent属性
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }
    try {
        // 3、创建AbstractBeanDefinition对象
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        // 4、解析bean标签属性
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        // 5、解析meta标签
        parseMetaElements(ele, bd);
        // 6、解析lookup-method属性
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // 7、解析replace-method属性
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        // 8、解析构造函数参数
        parseConstructorArgElements(ele, bd);
        // 9、解析property属性
        parsePropertyElements(ele, bd);
        // 10、解析qualifier属性
        parseQualifierElements(ele, bd);
        // 11、设置bean定义来源和元数据的来源
        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;
}

该方法涉及的细节较多,篇幅限制,只对其中比较重要比较常见的方法进行分析。分析第四步解析bean标签属性。

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

    // 1、设置bean作用域
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    } else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        // 未明确指定bean的作用域,且当前被解析bean是内部bean的话,
        // 则默认使用outer bean的的作用域作为当前bean的作用域
        // 例如:下面的配置,解析到inner属性时,inner未指定作用域,则使用outer的作用域,也就是prototype
        /**
        <bean id="outer" class="com.lyc.cn.v2.day01.inner.Outer" scope="prototype">
            <property name="inner">
                <bean id="inner" class="com.lyc.cn.v2.day01.inner.Inner"/>
            </property>
         </bean>
         **/
        bd.setScope(containingBean.getScope());
    }

    // 2、设置abstract属性
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    // 3、设置lazy-init(延迟加载)属性;
    // 如果该属性为true的话,ApplicationContext容器在初始化时不会加载该bean;
    // 而是在第一次向容器索取该bean时才会被初始化
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (DEFAULT_VALUE.equals(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    // 4、设置autowire属性,此属性默认不开启
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));

    // 5、设置depends-on属性,如果BeanA依赖于BeanB,可通过depends-on属性使BeanB在BeanA之前完初始化
    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }

    // 6、设置autowire-candidate属性,当一个接口有多个实现类时,
    // 配置autowire-candidate属性可以明确指定实现类是否参与自动注入
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if (candidatePattern != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
            bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    } else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }

    // 7、设置primary属性,当byType注入有多个类型时,
    // 可以指定primary="true",提高注入的优先级,避免抛出异常
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }

    // 8、设置init-method,bean初始化完成后回调该方法
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        bd.setInitMethodName(initMethodName);
    }
    else if (this.defaults.getInitMethod() != null) {
        // 尝试从DocumentDefaultsDefinition对象中获取init-method属性
        // DocumentDefaultsDefinition对象保存了Spring bean的一些简单设置,
        // 我们可以通过该类设定通用的bean属性模板
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
    }

    // 9、设置destroy-method属性,bean销毁后回调该方法
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        bd.setDestroyMethodName(destroyMethodName);
    }
    else if (this.defaults.getDestroyMethod() != null) {
        // 尝试从DocumentDefaultsDefinition对象中获取destroy-method属性
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
    }

    // 10、设置factory-method属性,该属性可指定静态工厂或实例工厂方法实例化bean
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    // 11、设置factory-bean属性,一般和factory-method属性一起使用,
    // 指定工厂bean和工厂bean的工厂方法
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }

    return bd;
}

其他的方法大家可以通过在配置文件中配置属性,debug跟踪调试,不在一一赘述。到这里Spring已经将xml中的配置信息封装至BeanDefinitionHolder对象,接下来就可以执行对bean的注册了。

另外对bean name的生成策略简单分析一下:
上面parseBeanDefinitionElement方法已经有所简介,来看一下具体过程:

public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws
            BeanDefinitionStoreException {

    // 1、获取bean的className
    String generatedBeanName = definition.getBeanClassName();

    // 2、如果generatedBeanName为null,
    // 则判断其是否有parent属性或者factory-bean并生成generatedBeanName
    if (generatedBeanName == null) {
        // parent属性不为空
        if (definition.getParentName() != null) {
            generatedBeanName = definition.getParentName() + "$child";
        }
        // factory-bean属性不为空
        else if (definition.getFactoryBeanName() != null) {
            generatedBeanName = definition.getFactoryBeanName() + "$created";
        }
    }
    // 3、如果上述方法均无法生成generatedBeanName,则抛出异常
    if (!StringUtils.hasText(generatedBeanName)) {
        throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither "
                + "'class' nor 'parent' nor 'factory-bean' - can't "
                + "generate bean name");
    }

    // 4、判断是否内部bean,并根据生成的generatedBeanName,拼接最终的beanName返回
    String id = generatedBeanName;
    if (isInnerBean) {
        // Inner bean: generate identity hashcode suffix.
        // GENERATED_BEAN_NAME_SEPARATOR-->#
        id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
    } else {
        // Top-level bean: use plain class name.
        // Increase counter until the id is unique.
        int counter = -1;
        while (counter == -1 || registry.containsBeanDefinition(id)) {
            counter++;
            // GENERATED_BEAN_NAME_SEPARATOR-->#
            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
        }
    }
    return id;
}

这一部分的代码比较多,也比较细,一时间找不到应该如何写才能让读者更清晰的了解解析过程,感兴趣的多多调试跟踪代码吧。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,321评论 6 543
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,559评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,442评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,835评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,581评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,922评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,931评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,096评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,639评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,374评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,591评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,104评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,789评论 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,196评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,524评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,322评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,554评论 2 379

推荐阅读更多精彩内容