Dubbo架构及源码分析(三)启动解析、加载配置信息

Container详解

Dubbo的Container是一个独立的容器,因为Dubbo服务通常不需要部署在Web容器(如Tomcat、JBoss等)中,没有必要用Web容器去加载服务,服务容器只是一个简单的Main方法,并且在一个简单的Spring容器用于暴露服务。

org.apache.dubbo.container.Container是服务启动的主类源码:

package org.apache.dubbo.container;

import org.apache.dubbo.common.extension.SPI;

/**
* Container. (SPI, Singleton, ThreadSafe)
*/
@SPI("spring")
public interface Container {

  /**
   * start method to load the container.
   */
  void start();

  /**
   * stop method to unload the container.
   */
  void stop();

}

通过以上代码可以看到,这个接口有两个方法,start()方法和stop()方法,它的实现类有Log4jContainer、LogBackContainer和SpringContainer,由于该接口上有@SPI(“spring”)注解,所以默认调用SpringContainer。
那么接下来看一下SpringContainer的源码:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.dubbo.container.spring;

import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.container.Container;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * SpringContainer. (SPI, Singleton, ThreadSafe)
 *
 * The container class implementation for Spring
 */
public class SpringContainer implements Container {
    /**读取dubbo.properties配置文件中dubbo.spring.config”的参数*/
    public static final String SPRING_CONFIG = "dubbo.spring.config”;
    /**默认加载的Spring配置文件路径*/
    public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
    private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
    static ClassPathXmlApplicationContext context;

    public static ClassPathXmlApplicationContext getContext() {
        return context;
    }

    @Override
    public void start() {
        String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
        /**如果参数为空,则默认加载META-INF/spring路径下的所有配置文件*/
        if (StringUtils.isEmpty(configPath)) {
            configPath = DEFAULT_SPRING_CONFIG;
        }
        context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
        context.refresh();
        //加载完配置文件之后,Spring容器开始启动
        context.start();
    }

    @Override
    public void stop() {
        try {
            if (context != null) {
                context.stop();
                context.close();
                context = null;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

}

容器在启动时,先去读取dubbo.properties文件,如果dubbo.properties文件中没有配置dubbo.spring.config 参数,则默认加载META-INF/spring下的全部Spring配置文件。

SpringContainer加载完配置文件之后,Spring容器开始启动,此时Dubbo服务的整个启动过程结束。

2. Dubbo-provider服务的配置文件:

在resources目录下,重点需要两个配置文件:
1、dubbo-consumer.xml
2、dubbo.properties
在项目启动时,Spring容器会加载这两个配置文件,Dubbo框架中的dubbo-config模块会解析这些配置文件,并且解析成对应的Bean定义,并注册到Spring上下文中。那么接下来我们分析,这些配置文件到底是怎么被解析成对应的Bean,并且注册到Spring的上下文当中的。

3.Dubbo服务的启动类:

想要了解配置文件被加载的过程,那么我们首先从项目启动的入口着手进行分析。接下来,我们看下Dubbo服务启动类的代码:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.dubbo.demo.provider;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {
    public static void main(String[] args) throws Exception {
        /**创建Spring IOC容器*/
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
        context.start();
        System.in.read();
    }
}

启动类的关键代码,加载Sring的IOC容器,并且指定dubbo的配置文件,那么当Spring容器启动的时候,会加载dubb-provider.xml文件。

4.Dubbu服务配置文件的加载过程

首先我们来看一下dubbo-config模块的整体架构:

Dubbo架构及源码分析(三)启动解析、加载配置信息_第1张图片

如图所示,dubbo-config模块分为两块,一个是dubbon-config-api和dubbo-config-spring,其中dubbo-config-api提供了一些接口和封装了一些实体类,dubbo-config-spring重点实现的是在spring容器启动时,加载并且解析dubbo项目的配置文件。

基于schema设计解析:

dubbo-config-spring的META-INF下有三个配置文件: dubbo.xsd、spring.handlers、spring.schemas文件。那么我们分别看下这三个配置文件的作用:
dubbo.xsd文件:规范了在编写xml文件时需要有哪些元素,以及元素的节点是什么,也就是对我们的provider服务中的dubbo-provider.xml文件做了一个约束。
spring.schemas文件:

http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd

文件中指定了dubbo的约束文件,Spring框架通过spring.handlers中的配置来解析用户的自定义配置,而dubbo.xsd文件正是用户自定义的一种约束规范,所以在此指定dubbo.xsd文件 Spring就可以解析xsd文件中配置的节点。
spring.handlers文件:

http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

指定了解析配置文件中标签的解析类。
Spring解析项目中dubbo-provider.xml的过程可以这么理解:
1、加载dubbo-provider.xml
2、解析到自定义的namespace(如 标签)时查找对应的spring.schemas和spring.handlers文件
3、spring.schemas文件指定了约束文件,spring.handlers指定了解析标签的类及DubboNamespaceHandler来进行初始化和解析。
dubbo.xsd文件中定义了很多模块,这些模块基本可以满足大多数使用场景。

基于XML配置解析原理:

通过以上分析我们可以得知,最终解析xml文件的是DubboNamespaceHandler这个类,接下来我们看下DubboNamespaceHandler的实现:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.dubbo.config.spring.schema;

import org.apache.dubbo.common.Version;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.MetadataReportConfig;
import org.apache.dubbo.config.ModuleConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.MonitorConfig;
import org.apache.dubbo.config.MetricsConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.spring.ConfigCenterBean;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.config.spring.ServiceBean;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * DubboNamespaceHandler
 *
 * @export
 */
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    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("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.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 AnnotationBeanDefinitionParser());
    }

}

DubboNamespaceHandler的继承图:

Dubbo架构及源码分析(三)启动解析、加载配置信息_第2张图片

DubboNamespaceHandler集成了NamespaceHandlerSupport,因此不需要实现全部的解析工作,只需要将自定义schema中的元素解析器注册进来就可以。
DubboBeanDefinitionParser类实现了BeanDefinitionParser这个接口,负责将标签转换成bean定义对象BeanDefinition。
我们接下来看一下DubboBeanDefinitionParser的parser方法解析步骤:

private static BeanDefinition parse(Element element, ParserContext parserContext, Class beanClass, boolean required) {
    /**初始化RootBeanDefinition,生成Spring的bean定义,指定beanClass交给Spring反射创建实例*/
    RootBeanDefinition beanDefinition = new RootBeanDefinition();
    beanDefinition.setBeanClass(beanClass);
    beanDefinition.setLazyInit(false);
    String id = element.getAttribute("id”);
    /**确保Spring容器中没有重复的Bean定义*/
    if (StringUtils.isEmpty(id) && required) {
        /**依次尝试获取XML文件配置标签的name和interface属性做为Bean的唯一Id*/
        String generatedBeanName = element.getAttribute("name");
        if (StringUtils.isEmpty(generatedBeanName)) {
                /**如果协议中没有指定名称,则默认为Dubbo*/
            if (ProtocolConfig.class.equals(beanClass)) {
                generatedBeanName = "dubbo";
            } else {
                generatedBeanName = element.getAttribute("interface");
            }
        }
        if (StringUtils.isEmpty(generatedBeanName)) {
            generatedBeanName = beanClass.getName();
        }
        id = generatedBeanName;
        int counter = 2;
        while (parserContext.getRegistry().containsBeanDefinition(id)) {
            id = generatedBeanName + (counter++);
        }
    }
    if (StringUtils.isNotEmpty(id)) {
        if (parserContext.getRegistry().containsBeanDefinition(id)) {
            throw new IllegalStateException("Duplicate spring bean id " + id);
        }
        /**每次解析回想Sring注册心的BeanDefinition,后续会追加属性*/
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        beanDefinition.getPropertyValues().addPropertyValue("id", id);
    }
    /**标签解析*/
    if (ProtocolConfig.class.equals(beanClass)) {
        for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
            BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
            PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
            if (property != null) {
                Object value = property.getValue();
                if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                    definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
                }
            }
        }
    } 
    /**标签解析*/
    else if (ServiceBean.class.equals(beanClass)) {
        String className = element.getAttribute("class");
        if (className != null && className.length() > 0) {
            RootBeanDefinition classDefinition = new RootBeanDefinition();
            classDefinition.setBeanClass(ReflectUtils.forName(className));
            classDefinition.setLazyInit(false);
            parseProperties(element.getChildNodes(), classDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
        }
    } 
    /**标签解析*/
    else if (ProviderConfig.class.equals(beanClass)) {
        parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
    } 
    /**标签解析*/
    else if (ConsumerConfig.class.equals(beanClass)) {
        parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
    }
    Set props = new HashSet<>();
    ManagedMap parameters = null;
    for (Method setter : beanClass.getMethods()) {
        String name = setter.getName();
        if (name.length() > 3 && name.startsWith("set")
                && Modifier.isPublic(setter.getModifiers())
                && setter.getParameterTypes().length == 1) {
            Class type = setter.getParameterTypes()[0];
            String beanProperty = name.substring(3, 4).toLowerCase() + name.substring(4);
            String property = StringUtils.camelToSplitName(beanProperty, "-");
            props.add(property);
            // check the setter/getter whether match
            Method getter = null;
            try {
                getter = beanClass.getMethod("get" + name.substring(3), new Class[0]);
            } catch (NoSuchMethodException e) {
                try {
                    getter = beanClass.getMethod("is" + name.substring(3), new Class[0]);
                } catch (NoSuchMethodException e2) {
                    // ignore, there is no need any log here since some class implement the interface: EnvironmentAware,
                    // ApplicationAware, etc. They only have setter method, otherwise will cause the error log during application start up.
                }
            }
            if (getter == null
                    || !Modifier.isPublic(getter.getModifiers())
                    || !type.equals(getter.getReturnType())) {
                continue;
            }
            if ("parameters".equals(property)) {
                parameters = parseParameters(element.getChildNodes(), beanDefinition);
            } else if ("methods".equals(property)) {
                parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
            } else if ("arguments".equals(property)) {
                parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
            } else {
                String value = element.getAttribute(property);
                if (value != null) {
                    value = value.trim();
                    if (value.length() > 0) {
                        if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
                            RegistryConfig registryConfig = new RegistryConfig();
                            registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
                            beanDefinition.getPropertyValues().addPropertyValue(beanProperty, registryConfig);
                        } else if ("provider".equals(property) || "registry".equals(property) || ("protocol".equals(property) && ServiceBean.class.equals(beanClass))) {
                            /**
                             * For 'provider' 'protocol' 'registry', keep literal value (should be id/name) and set the value to 'registryIds' 'providerIds' protocolIds'
                             * The following process should make sure each id refers to the corresponding instance, here's how to find the instance for different use cases:
                             * 1. Spring, check existing bean by id, see{@link ServiceBean#afterPropertiesSet()}; then try to use id to find configs defined in remote Config Center
                             * 2. API, directly use id to find configs defined in remote Config Center; if all config instances are defined locally, please use {@link org.apache.dubbo.config.ServiceConfig#setRegistries(List)}
                             */
                            beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids", value);
                        } else {
                            Object reference;
                            if (isPrimitive(type)) {
                                if ("async".equals(property) && "false".equals(value)
                                        || "timeout".equals(property) && "0".equals(value)
                                        || "delay".equals(property) && "0".equals(value)
                                        || "version".equals(property) && "0.0.0".equals(value)
                                        || "stat".equals(property) && "-1".equals(value)
                                        || "reliable".equals(property) && "false".equals(value)) {
                                    // backward compatibility for the default value in old version's xsd
                                    value = null;
                                }
                                reference = value;
                            } else if(ONRETURN.equals(property) || ONTHROW.equals(property) || ONINVOKE.equals(property)) {
                                int index = value.lastIndexOf(".");
                                String ref = value.substring(0, index);
                                String method = value.substring(index + 1);
                                reference = new RuntimeBeanReference(ref);
                                beanDefinition.getPropertyValues().addPropertyValue(property + METHOD, method);
                            } else {
                                if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
                                    BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
                                    if (!refBean.isSingleton()) {
                                        throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: ");
                                    }
                                }
                                reference = new RuntimeBeanReference(value);
                            }
                            beanDefinition.getPropertyValues().addPropertyValue(beanProperty, reference);
                        }
                    }
                }
            }
        }
    }
    NamedNodeMap attributes = element.getAttributes();
    int len = attributes.getLength();
    for (int i = 0; i < len; i++) {
        Node node = attributes.item(i);
        String name = node.getLocalName();
        if (!props.contains(name)) {
            if (parameters == null) {
                parameters = new ManagedMap();
            }
            String value = node.getNodeValue();
            parameters.put(name, new TypedStringValue(value, String.class));
        }
    }
    if (parameters != null) {
        beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
    }
    return beanDefinition;
}

Dubbo通过parse方法解析配置文件,并将配置文件的各个参数映射到对应的JavaBean

你可能感兴趣的:(Dubbo,spring,java)