Spring源码阅读之-自定义配置的解析

Spring源码阅读之-自定义配置的解析

上文书:《Spring源码阅读之-bean的解析与注册》说到,Spring根据delegate.parseCustomElement(ele); 方法来解析自定义命名空间的element节点,本篇博文即分析该解析方式是如何工作的。
入口接上文分析,代码在

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root,
                                    @NotNull BeanDefinitionParserDelegate delegate)
/**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 验证Doc文件的命名空间
        if (delegate.isDefaultNamespace(root)) { //又来判断
            //获得根元素(<beans>)的所有子元素
            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)) { 
                        //解析<beans>子元素的内容
                        parseDefaultElement(ele, delegate); 
                    }
                    else {
                        delegate.parseCustomElement(ele); //否则解析自定义节点
                    }
                }
            }
        }
        else { //按自定义节点解析
            delegate.parseCustomElement(root);
        }
    }

上边这段代码的含义上文已经分析过,这里主要看delegate.parseCustomElement(ele); 这句代码,其语义含义为使用委托类来解析自定义节点,该自定义节点是如何进行解析的,跟着代码往下看。

public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);//获得元素的命名空间
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); //根据指定的命名空间找到一个NamespaceHandler 
        if (handler == null) { //如果没有找到对应的handler,打印错误日志 
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));//返回handler的解析类

    }

以上代码可已看出,Spring读取配置时根据上下文中维护的NamespaceHandlerResolver,调用resolver 来返回适用于该节点命名空间的handler,那么Spring是如何维护,namespaceUri与handler的对应关系的呢?
*这个parseCustomElement方法虽说返回一个BeanDefinition ,但是不难发现,Spring并没有进行处理。也就是说有可能需要自己在NamespaceHandler.parse实现中注册beanDefinition

namespaceUri与NamespaceHandler 的对应关系

接以上代码段,在context对象中找到对NamespaceHandlerResolver的维护,发现其在Context中是一个org.springframework.beans.factory.xml.NamespaceHandlerResolver类型的实例变量,ctrl+alt+b 发现其只有一个实现类org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver,其实该类的初始化是在创建context时完成的,初始化代码如下

    /**
     * Create the {@link XmlReaderContext} to pass over to the document reader.
     */
    public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

    /**
     * Lazily create a default NamespaceHandlerResolver, if not set before.
     * @see #createDefaultNamespaceHandlerResolver()
     */
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }
    /**
     * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified.
     * Default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}.
     */
    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
    }

以上只为说明,在创建读取xml的上下文对象时readerContext中使用的DefaultNamespaceHandlerResolver的初始化过程。DefaultNamespaceHandlerResolver初始化时维护一个public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
把该文件打开看看

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

根据以上文件内容去观察这个意图就很明显了,这根本就是将namespaceUri与NamespaceHandler 的对应关系维护在一个”META-INF/spring.handlers”这样的文件中,然后DefaultNamespaceHandlerResolver的resolver方法在该配置文件中根据namespaceUri获得NamespaceHandler对象,resolver方法内部就不详细解释了,毕竟Spring各功能这么繁杂也就只有一个DefaultNamespaceHandlerResolver来处理各种handlers而已,根本不需要我们来实现,只需要知道Spring通过spring.handlers文件来声明自定义节点的命名空间和其对应的处理类关系即可(DefaultNamespaceHandlerResolver类代码很简单,有兴趣可以自己看看)。
注意:书写时注意要对‘:’符号做转义,类名要写全。注册自己的Handler也可以通过这种方式来实现,只需要在自己的包中同样位置放置一个spring.handlers 文件,Spring即可自行加载其中配置。
很多常用的框架采用这种方式,向Spring中注册自己的一些信息,例如dubbo,看看有哪些常用包有spring.handlers 文件
这里写图片描述

小结:要使Spring认识自己声明的命名空间,需要在jar包的META-INF目录下,放置一个spring.handlers文件,并以命名空间=类名的方式书写规则,注意:自己的命名空间Handler类要实现org.springframework.beans.factory.xml.NamespaceHandler接口

命名空间下节点内容的解析

上节说名了如何注册一个自己的命名空间Handler,一个命名空间下往往有几个不同的节点定义,就拿Spring的“http://www.springframework.org/schema/context”空间说事吧,其定义了几种不同的element,例如大名鼎鼎的annotation-config和component-scan(具体其空间下有几种element可以去看对应的xsd文件)
这里用个偷懒的方式去看,上节我们知道spring对自定义命名空间的处理是通过在spring.handlers中维护nameSpaceUri与NamespaceHandler的对应关系来实现的。那么NamespaceHandler就一定会有针对该空间的element的处理。这里依然去看大名鼎鼎的“http://www.springframework.org/schema/context”其对应的处理类为“org.springframework.context.config.ContextNamespaceHandler”,这个类打开来也很简单,很好懂,8个字“欠债还钱,杀人偿命”等等,好像走错片场了。

/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.config;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser;
import org.springframework.context.annotation.ComponentScanBeanDefinitionParser;

/**
 * {@link org.springframework.beans.factory.xml.NamespaceHandler}
 * for the '{@code context}' namespace.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @since 2.5
 */
public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }

}

不他娘的管继承了,直接去看他的行为就行了,反正最后都是org.springframework.beans.factory.xml.NamespaceHandler接口中定义的行为,不过该内容也反映了几个特征,他针对了不同节点,注册了不同的Parser类进来。这里不多说,可能自己实现的时候不会跟其一样,但是这是一种很值得学习的思路。即不同的节点交给不同的解析类来解析,与框架中的委托类所做的很像。其解析器的统一实现的接口为org.springframework.beans.factory.xml.BeanDefinitionParser 感兴趣的时候可以看一下。这里主要还是看会被 delegate.parseCustomElement(root); 调用的NamespaceHandler接口的parse方法。渣英文不翻译了,很好理解,直接看看大佬们怎么玩的,更好说明问题。

/**
     * Parse the specified {@link Element} and register any resulting
     * {@link BeanDefinition BeanDefinitions} with the
     * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
     * that is embedded in the supplied {@link ParserContext}.
     * <p>Implementations should return the primary {@code BeanDefinition}
     * that results from the parse phase if they wish to be used nested
     * inside (for example) a {@code <property>} tag.
     * <p>Implementations may return {@code null} if they will
     * <strong>not</strong> be used in a nested scenario.
     * @param element the element that is to be parsed into one or more {@code BeanDefinitions}
     * @param parserContext the object encapsulating the current state of the parsing process
     * @return the primary {@code BeanDefinition} (can be {@code null} as explained above)
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

大佬:ContextNamespaceHandler 很牛逼的一个自定义节点处理类了,其支持了Annotation注解的方式,其处理逻辑在org.springframework.context.annotation.ComponentScanBeanDefinitionParser类中,不多说他,总之就是扫描注解->转BeanDefinetion->注册到context中的Registry的过程。可能有时间得单独说明一下。以下是比较重要的一段。


    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
        basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
        String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

        // Actually scan for bean definitions and register them.
        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

        return null;
    }
protected void registerComponents(
            XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

        //...................//
            Set<BeanDefinitionHolder> processorDefinitions =
                    AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
        //...................//
        readerContext.fireComponentRegistered(compositeDef);
    }
    /**
     * Register all relevant annotation post processors in the given registry.
     * @param registry the registry to operate on
     * @param source the configuration source element (already extracted)
     * that this registration was triggered from. May be {@code null}.
     * @return a Set of BeanDefinitionHolders, containing all bean definitions
     * that have actually been registered by this call
     */
    public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, Object source) {

        DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
        if (beanFactory != null) {
            if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
                beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
            }
            if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
                beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
            }
        }

        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);

        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        //...........................//
        return beanDefs;
    }

    private static BeanDefinitionHolder registerPostProcessor(
            BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {

        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(beanName, definition);
        return new BeanDefinitionHolder(definition, beanName);
    }

*可见:beanDefinition得自己注册到BeanFactory中,如果自己实现NamespaceHandler时不要遗漏注册的动作,否则白写了[滑稽]
大佬:dubbo
叱咤分布式系统中,风光无限,其对自己的nameSpace的管理,可以看到与Spring大佬的写法一致。也是针对不同的element搞了好多Parser,后续有机会在深究下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.alibaba.dubbo.config.spring.schema;


public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    public DubboNamespaceHandler() {
    }

    public void init() {
        this.registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        this.registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        this.registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        this.registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        this.registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        this.registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        this.registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值