Spring5源码(17)-Spring解析xml默认命名空间

简介: Spring5源码(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;
}

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





目录
相关文章
|
5天前
|
运维 Java 程序员
Spring5深入浅出篇:Spring切入点详解
该文档是关于Spring框架中切入点的详细解释。切入点是AOP(面向切面编程)的核心概念,用于定义通知(如日志、事务管理)应该附加到代码的哪些位置。文档主要介绍了切入点表达式的不同类型: 1. 方法切入点表达式:使用`execution()`定义匹配的方法,星号`*`代表任意返回值和方法名,`(..)`表示任意参数。 2. 类切入点:指定特定类以应用额外功能,可以精确到类中的所有方法,或者只包含特定包的类。 3. 包切入点表达式:适用于整个包或包及其子包内的所有类和方法。
|
1天前
|
XML 存储 数据格式
探索 DTD 在 XML 中的作用及解析:深入理解文档类型定义
DTD(文档类型定义)用于定义XML文档的结构和合法元素、属性。它允许不同团体就数据交换标准达成一致,并用于验证XML数据的有效性。DTD可通过内部声明(在XML文档内)或外部声明(在单独文件中)来定义。内部声明示例显示了如何定义`note`元素及其子元素的结构,而外部声明则引用外部DTD文件。元素、属性、实体和PCDATA/CDATA是XML构建模块。DTD中,元素通过ELEMENT声明定义,属性通过ATTLIST声明定义,实体声明提供特殊字符或外部资源的快捷方式。
5 0
|
2天前
|
canal 缓存 关系型数据库
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
|
3天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
6天前
|
XML Java 数据库连接
Javaweb之Mybatis的XML配置文件的详细解析
Javaweb之Mybatis的XML配置文件的详细解析
13 0
|
9天前
|
XML C# 数据格式
C# 解析XML文件
C# 解析XML文件
17 1
|
10天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
15天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
21 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
15天前
|
XML Java 数据格式
从入门到精通:Spring基础注解的全面解析
从入门到精通:Spring基础注解的全面解析
32 2
从入门到精通:Spring基础注解的全面解析
|
6天前
|
XML 数据格式
小米备份descript.xml文件
小米备份descript.xml文件
12 0