• [spring源码学习]三、IOC源码——自定义配置文件读取


    一、环境准备

      在文件读取的时候,第9步我们发现spring会根据标签的namespace来选择读取方式,联想spring里提供的各种标签,比如<aop:xxx>等应该会有不同的读取和解析方式,这一章我们来找一个其他文件,了解下spring自定义标签和配置的读取流程。

      手边正好有一套dubbo的源码,因此为了区别与spring的原生读取,就使用它来进行分析。

      首先spring的配置文件中我们需要加上标签的namespace

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    <!--dubbo的命名空间-->
     xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            <!--dubbo的解析文件-->
            http://code.alibabatech.com/schema/dubbo
            http://code.alibabatech.com/schema/dubbo/dubbo.xsd
            ">
    
     <dubbo:application name="xixi_provider" />
    
    </beans>

    二、源码分析

    1、我们回到上次的第9步

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
            if (delegate.isDefaultNamespace(root)) {
                NodeList nl = root.getChildNodes();
                for (int i = 0; i < nl.getLength(); i++) {
                    Node node = nl.item(i);//[dubbo:application: null]
                    if (node instanceof Element) {
                        Element ele = (Element) node;
                        if (delegate.isDefaultNamespace(ele)) {
                            parseDefaultElement(ele, delegate);
                        }
                        else {
                            delegate.parseCustomElement(ele);//namespace不是默认的,此处为入口
                        }
                    }
                }
            }
            else {
                delegate.parseCustomElement(root);
            }
        }

    2、得到标签对应的namespace,

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
            String namespaceUri = getNamespaceURI(ele);//http://code.alibabatech.com/schema/dubbo
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);//获取NamespaceHandlerResolver,并根据namespace找到对应的handler
            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));
        }

    3、获取NamespaceHandlerResolver的流程如下:

    a)找到所有的配置文件META-INF/spring.handlers,并读取,将他们转化为handlerMappings

        private Map<String, Object> getHandlerMappings() {
            if (this.handlerMappings == null) {
                synchronized (this) {
                    if (this.handlerMappings == null) {
                        try {
                            Properties mappings =
                                    PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                            }
                            Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                            CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                            this.handlerMappings = handlerMappings;
                        }
                        catch (IOException ex) {
                            throw new IllegalStateException(
                                    "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                        }
                    }
                }
            }
            return this.handlerMappings;
        }

    b)从handlerMapping中获取到标签对应的handler名称,此处为:com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

    我们可以到dubbo项目中查找,发现spring.handlers内容为:

      http://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

    与代码一致

    c)利用反射,将类进行初始化,并保存在handlerMapping,下次可以直接使用

    d)调用初始化init方法,我们进入init方法,看到应该是针对每个标签做了一个BeanDefinitionParser方法进行注入到了系统中

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

    4、根据标签的localName,获得解析标签的BeanDefinitionParser

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
            String localName = parserContext.getDelegate().getLocalName(element);
            BeanDefinitionParser parser = this.parsers.get(localName);
            if (parser == null) {
                parserContext.getReaderContext().fatal(
                        "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
            }
            return parser;
        }

    5、调用parser方法,因为dubbo的程序根据注入的beanClass和required进行解析,自己写程序时候,可以每个标签可以使用独立的parser类

        public BeanDefinition parse(Element element, ParserContext parserContext) {
            return parse(element, parserContext, beanClass, required);
        }

    6、进入parser方法后,初始化beanDefinition,设置默认值

            RootBeanDefinition beanDefinition = new RootBeanDefinition();
            beanDefinition.setBeanClass(beanClass);
            beanDefinition.setLazyInit(false);

    7、如果没有且必须处理id,并注册

                parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
                beanDefinition.getPropertyValues().addPropertyValue("id", id);

    8、获取所有的字段ApplicationConfig,从节点中读取是否有值,如果有值进行注入

    三、总结

    看了dubbo的配置文件解析后,我们基本可以得出结论,spring中新增一种标签需要做以下工作:

    1、定义namespace

    2、编写NamespaceHandler并写入spring.handlers,指定namesapce对应的关系

    3、在parser的init方法中,针对每种标签指定解析类DubboBeanDefinitionParser

    定义完以上步骤后,spring会自动调用解析,解析的结果与默认的bean一致,均为BeanDefinition,所有标签自定义属性均解析到pv中,也就是自定义标签定义标签的外观,并未改变spring对bean的解析结果和ioc中实例化过程。

  • 相关阅读:
    sychronized面试问题浅析
    打造一款属于自己的web服务器——开篇
    JVM学习总结五——性能监控及故障处理工具
    【转】Git常用命令备忘
    HTTP上传文件探究
    JVM学习总结四——内存分配策略
    JVM学习总结三——垃圾回收器
    JVM学习总结二——垃圾回收算法
    Vue生命周期中mounted和created的区别
    spring org.springframework.web.bind.annotation 常用注解
  • 原文地址:https://www.cnblogs.com/jyyzzjl/p/5431788.html
Copyright © 2020-2023  润新知