dubbo源码分析

SPI

Dubbo采用微内核+插件体系。设计优雅,可扩展性强。
微内核+插件体系的实现是基于SPI机制。(service provider interface)
框架定义服务标准接口,使用者可以自己扩展服务实现。

1, Dubbo定义了@SPI注解
对于打了@spi注解的接口,dubbo会从以下目录依次查找实现。
META-INF/dubbo/internal/ //dubbo 内部实现的各种扩展都放在了这个目录

META-INF/dubbo/
META-INF/services/

2, ExtensionLoader是SPI加载器。
com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses

java中的SPI

全称是Service Provider Interface。和开发人员面对的interface是类似的概念,给商家或者插件提供商开放不同的服务实现。

约定:
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。java.util.ServiceLoader会查找这个路径,找到实现。

服务暴露

关注com.alibaba.dubbo.config.spring.ServiceBean
管理服务的生命周期。

  1. setApplicationContext 设置容器 applicationContext
  2. afterPropertiesSet 回调InitializingBean 的 afterPropertiesSet
  3. onApplicationEvent spring的最后一步 finishRefresh 会触发 ContextRefreshedEvent 事件,而 ServiceBean
    实现了 ApplicationListener 接口监听了此事件, 而在之前一步实例化的
    ServiceBean 注 册 了 这 个 事 件 , 所 以 ServiceBean 的
    onApplicationEvent(ApplicationEvent event)方法被触发, 在这个方法中触
    发了 export 方法来暴露服务。
  4. destroy 服务下线

export

看一下export具体是怎么实现的

  1. ServiceBean实现了ApplicationListener接口,监听容器加载完成事件ContextRefreshedEvent。
    开始export()
  2. com.alibaba.dubbo.config.ServiceConfig#export()会判断是否是延迟暴露。
    是则起一个守护线程Thread.sleep(delay)后暴露,否则直接暴露。
  3. com.alibaba.dubbo.config.ServiceConfig#doExport().装载注册中心监控中心等
  4. com.alibaba.dubbo.config.ServiceConfig#doExportUrls,
    执行loadRegistries()遍历注册中心,根据注册中心、Dubbo版本、Pid等生成要发布的URL;
    URL示例: zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=ordercenter_serviceImpl
    &dubbo=2.8.4&pid=15836®istry=zookeeper×tamp=1484018365125
    遍历所有协议,遍历服务协议,为每个协议执行doExportUrlsFor1Protocol()
  5. com.alibaba.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol

    Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
    Exporter exporter = protocol.export(invoker);

    关键的两句,

    1. 将具体的服务转换成invoker,
      ref是接口实现类引用,interfaceClass是接口,url是组装的服务url。
      转换使用JavassistProxyFactory或者JdkProxyFactory中一个。
    2. 然后将invoker转换成exporter。
      转化过程使用众多协议中的一个,DubboProtocol、hessionProtocol、redisProtocol等等
  6. 比如DubboProtocol
    export() -> openServer() ->createServer()
    createServer()中通过server = Exchangers.bind(url, requestHandler);
    代码转到com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
    最终转到创建NettyServer。

服务引用

关注com.alibaba.dubbo.config.spring.ReferenceBean
ReferenceBean实现了FactoryBean接口,spring的bean工厂在获取的bean的时候会判断下是不是FactoryBean的实例,如果是调factoryBean.getObject()返回,否则返回bean。
我们使用时并不是想要ReferenceBean这个实例,而是希望通过这个实例获取对应的代理,通过代理调用远程服务。
所以在factoryBean.getObject()中封装了复杂实现,使我们可以拿到代理对象。

  1. ReferenceBean实现InitializingBean接口,spring回调afterPropertiesSet()
  2. getObject()
  3. 检查dubbo配置,创建代理。
  4. 向注册中心订阅服务。
  5. 返回代理对象。

dubbo的spring配置是如何解析的?

META-INF/spring/dubbo-demo-provider.xml为例。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />

    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />

beans>

其中的dubbo:service是自定义的扩展schema,他是如何被spring解析的呢?
原理:spring支持自定义扩展schema。
具体原理:spring默认会加载两个文件:这两个文件的地址必须是META-INF/spring.handlers和META-INF/spring.schemas。
对应的dubbo,可在dubbo-config-spring下找到。

META-INF/spring.schemas的内容:
http://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
放的是自己定义的xsd文件位置。

META-INF/spring.handlers的内容:

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

这个类DubboNamespaceHandler是将xsd中定义的标签,找到对应的解析器。

handler要继承spring提供的NamespaceHandlerSupport。
DubboBeanDefinitionParser要实现spring提供的BeanDefinitionParser。

这样,spring就可以找到xsd以及对应的解析器。
最后在spring的xml中使用是,引入即可。

分析下DubboBeanDefinitionParser

  1. 需要实现spring提供的BeanDefinitionParser。
  2. 主要看parse方法。很长。主要做了这几件事。
  3. 创建bean id。具体逻辑不讲,看代码。然后注册到spring的上下文中,重复,抛错。
  4. 对某些类型做一下特殊处理,比如ServiceBean,需要将对应的实现注册到ref里。
  5. 然后获取class的set方法,判断是否可以set注入。通过set返货获取属性,进而得到get或者is方法。再从xml的element中拿到对应的值。最后组装到BeanDefinition中。spring通过BeanDefinition完成注入。

dubbo如何获取properties配置文件中的配置项

对于dubbo的任何Config类,比如ServiceConfig都是继承自AbstractConfig,这个类是配置解析的工具方法、公共方法。
关注appendProperties方法。

首先获取前缀。
String prefix = "dubbo." + getTagName(config.getClass()) + ".";

private static String getTagName(Class cls) {
        String tag = cls.getSimpleName();
        for (String suffix : SUFFIXS) {
            if (tag.endsWith(suffix)) {
                tag = tag.substring(0, tag.length() - suffix.length());
                break;
            }
        }
        tag = tag.toLowerCase();
        return tag;
    }


private static final String[] SUFFIXS = new String[] {"Config", "Bean"};

把结尾的Config或Bean截掉,比如ReferenceBean的配置项都会以dubbo.reference.开头。
然后拿到类的所有方法,执行其中的set方法,注入值。

 for (Method method : methods) {
            try {
                String name = method.getName();
                if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers()) 
                        && method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
                        ...
                        ...
                        method.invoke(config, new Object[] {convertPrimitive(method.getParameterTypes()[0], value)});
                        }
              }

注意,获取properties时,首先从System.getProperty(也就是java -D中指定的参数),再从ConfigUtils.getProperty中取(即properties文件中的配置)。
再注意,从 ConfigUtils.getProperty取之前,会通过get方法判断bean中该属性是否已经有值了(通过spring注入的),没有值才会从ConfigUtils.getProperty中取。
所以最终的优先级是:
System.getProperty —-> spring bean注入 —>ConfigUtils.getProperty

另外注意到:
从System.getProperty或者从ConfigUtils.getProperty中取都会取两次

                if (config.getId() != null && config.getId().length() > 0) {
                        String pn = prefix + config.getId() + "." + property;
                        value = System.getProperty(pn);
                        if(! StringUtils.isBlank(value)) {
                            logger.info("Use System Property " + pn + " to config dubbo");
                        }
                    }
                    if (value == null || value.length() == 0) {
                        String pn = prefix + property;
                        value = System.getProperty(pn);
                        if(! StringUtils.isBlank(value)) {
                            logger.info("Use System Property " + pn + " to config dubbo");
                        }
                    }

第一次带着config.getId(),这个id就是bean id。
就是说,先取针对这个bean的配置,没有再取通用配置。

动态配置

这种方式主要原理就是通过管理器将动态参数发布到注册中心(zookeeper)中,然后各个节点可以获得最新的配置变更,然后进行动态调整。
想知道这一块内容,需要取看看类RegistryDirectory的实现,该类它实现了NotifyListener。那么它就可以监听到注册中心的任何变更。再了解RegistryDirectory之前,先看看DUBBO对每个服务在注册中心存储了哪些信息?如果用的时zookeeper,那么你会在每个服务节点目录下面看到一下几个目录consumers,providers,configurators,routers。其中动态配置时放在configurators节点目录下。服务消费端会监听configurators目录变更,如果变更则会调用RegistryDirectory的void notify(List urls)方法。监听configurators目录变更触发的void notify(List urls)方法时,urls的是类似override://…,表示将覆盖调用该服务的某些配置(dubbo中对所有的调用配置都是通过URL的形式来展示的),讲这些URL上面的参数信息替换到调用服务端的URL上面取,并且重新构造该服务的Invoke对象,从而达到更新参数的目的。

[zk: localhost:2181(CONNECTED) 11] ls /dubbo/com.alibaba.dubbo.demo.DemoService
[consumers, configurators, routers, providers]

你可能感兴趣的:(dubbo)