dubbo源码解析

1. ExtensionLoader、@Adaptive、@Activate

ExtensionLoader

ExtensionLoader类是dubbo的SPI机制的核心类,区别于JDK的SPI机制以及spring的SPI机制,dubbo的SPI机制提供了一些更强的功能:

  • 自适应扩展
  • 根据条件激活扩展

dubbo内置的扩展都在dubbo-x.x.x.jar/META-INF/dubbo.internal目录下面,如果我们想替换dubbo的组件,可以在自己项目的/META-INF/dubbo目录或者/META-INF/services目录下编写自定义的组件文件就可以了,文件名称为接口的全名称,内容为key=实现类的全名称。

ExtensionLoader.getExtension(name)
ExtensionLoader.createExtension
ExtensionLoader.getExtensionClasses
ExtensionLoader.loadExtensionClasses
//这个方法从META-INF/dubbo/、META-INF/dubbo/internal/、META-INF/services/三个目录下面加载扩展实例
ExtensionLoader.loadFile
ExtensionLoader.injectExtension

@Adaptive

通过ExtensionLoader.getAdaptiveExtension()方法获取到的自适应扩展类,打上该annotation的扩展实现类会被获取到,如果没有类被打上该annotation,则会生成一个代理类,这个代理类的方法实际上是去调用@Spi(value=xxx)指定的默认的实现类方法(或者是URL参数指定的实现类的方法,优先级更高)。有了自适应扩展标签,就不需要硬编码来指定使用哪个扩展实现类了。

自适应扩展的使用方式有两个途径:

  • URL参数(优先级高)
  • 添加了@Adaptive标签的扩展实现类(优先级低)

代码示例

它具有以下特点:

  • 如果实现类中有某一个标记了@Adaptive,则这个实现类会被使用
  • 如果实现类全部都没有标记@Adaptive,则接口的方法必须有一个或多个标记@Adaptive,dubbo会动态生成一个代理类并实现打了@Adaptive标记的方法,如果方法都没有@Adaptive标签则抛错
    • 动态生成xxx$Adaptive类的逻辑在ExtensionLoader.createAdaptiveExtensionClassCode方法里面,示例:

      package com.qigang.da;
      import com.alibaba.dubbo.common.extension.ExtensionLoader;
          
      public class Registry$Adpative implements com.qigang.da.Registry {
          public java.lang.String register(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
              if (arg0 == null) throw new IllegalArgumentException("url == null");
              com.alibaba.dubbo.common.URL url = arg0;
              String extName = url.getParameter("registry", "zookeeper");
              if(extName == null) throw new IllegalStateException("Fail to get extension(com.qigang.da.Registry) name from url(" + url.toString() + ") use keys([registry])");
              com.qigang.da.Registry extension = (com.qigang.da.Registry)ExtensionLoader.getExtensionLoader(com.qigang.da.Registry.class).getExtension(extName);
              return extension.register(arg0, arg1);
          }
      
          public java.lang.String discovery(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
              if (arg0 == null) throw new IllegalArgumentException("url == null");
              com.alibaba.dubbo.common.URL url = arg0;
              String extName = url.getParameter("registry", "zookeeper");
              if(extName == null) throw new IllegalStateException("Fail to get extension(com.qigang.da.Registry) name from url(" + url.toString() + ") use keys([registry])");
              com.qigang.da.Registry extension = (com.qigang.da.Registry)ExtensionLoader.getExtensionLoader(com.qigang.da.Registry.class).getExtension(extName);
              return extension.discovery(arg0, arg1);
          }
      }
      
    • 代理类的方法中会判断URL中的参数(如果接口方法有URL参数就会解析这个URL参数值),决定最终调用的是哪个扩展实现类的方法,如果URL中没有指定,则使用接口@Spi(value=”xxx”)指定的默认实现类

    • URL中的参数key默认使用接口名称的英文单词小写,多个单词之间用点隔开,如果@Adaptive(value=”xxx”)指定了参数,那URL种的key就是指定的这个参数值

  • 具有@Adaptive标签的实现类的使用优先级高于URL中指定参数的方式

对@Adaptive的使用贯穿dubbo的各个组件,例如注册中心组件使用的接口

/**
 * RegistryFactory. (SPI, Singleton, ThreadSafe)
 * 
 * @see com.alibaba.dubbo.registry.support.AbstractRegistryFactory
 * @author william.liangf
 */
@SPI("dubbo")
public interface RegistryFactory {

    /**
     * 连接注册中心.
     * 
     * 连接注册中心需处理契约:<br>
     * 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。<br>
     * 2. 支持URL上的username:password权限认证。<br>
     * 3. 支持backup=10.20.153.10备选注册中心集群地址。<br>
     * 4. 支持file=registry.cache本地磁盘文件缓存。<br>
     * 5. 支持timeout=1000请求超时设置。<br>
     * 6. 支持session=60000会话超时或过期设置。<br>
     * 
     * @param url 注册中心地址,不允许为空
     * @return 注册中心引用,总不返回空
     */
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);

}

@Activate

通过ExtensionLoader.getActivateExtension()方法获取到激活的扩展类,也就是根据某种条件(group, key, order)来激活扩展类

示例代码

2. 服务暴露

xml配置方式暴露服务

dubbo的配置文件使用的是spring的XML扩展方式,在dubbo-x.x.x.jar/META-INF/spring.handlers文件中定义了处理xml文件的DubboNamespaceHandler,里面注册了很多类型的DubboBeanDefinitionParser,将xml配置文件中的不同节点解析成不同的BeanDefinition。各个BeanDefinition被加载进spring容器之后执行spring bean生命周期中的步骤。

各个配置节点的详细作用参考官方配置文档

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

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

    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 AnnotationBeanDefinitionParser());
    }
}

其中服务暴露主要是通过ServiceBean来完成的,它实现了InitializingBean,afterPropertiesSet方法会被首先执行。同时它还实现了ApplicationContextAware,setApplicationContext方法会被调用,它还实现了ApplicationListener,onApplicationEvent方法也会被调用。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware {

    private static final long serialVersionUID = 213195494150089726L;

    private static transient ApplicationContext SPRING_CONTEXT;

    private final transient Service service;

    private transient ApplicationContext applicationContext;

    private transient String beanName;

    private transient boolean supportedApplicationListener;

    public ServiceBean() {
        super();
        this.service = null;
    }

    public ServiceBean(Service service) {
        super(service);
        this.service = service;
    }

    public static ApplicationContext getSpringContext() {
        return SPRING_CONTEXT;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        //...
    }

    public void setBeanName(String name) {
        this.beanName = name;
    }

    public Service getService() {
        return service;
    }

    public void onApplicationEvent(ContextRefreshedEvent event) {
        //...
        //如果判断是延迟暴露服务,则调用export方法暴露服务
    }

    private boolean isDelay() {
        //...
    }

    @SuppressWarnings({"unchecked", "deprecation"})
    public void afterPropertiesSet() throws Exception {
        //...
        //主要是解析xml配置中的各项配置bean,最后如果判断为非延迟暴露则直接调用export方法暴露服务
    }

    public void destroy() throws Exception {
        //...
    }
}

afterPropertiesSetonApplicationEvent中分别调用了ServiceConfig.export()方法来暴露服务,这里有个问题,两个地方都有export方法,哪个会被调用?跟踪调试会发现,如果spring容器可以成功添加ServiceBean为监听器,也就是可以成功通过反射执行容器的addApplicationListener或者addListener方法,那就不会在afterPropertiesSet方法中执行export方法,而是在onApplicationEvent中执行,否则就执行afterPropertiesSet方法中的export。

ServiceConfig.export()
ServiceConfig.doExport()
ServiceConfig.doExportUrls()
ServiceConfig.doExportUrlsFor1Protocol()
    //根据配置的参数生成URL对象,例如
    //dubbo://172.17.125.18:20880/com.qigang.dubbo.service.DemoService?anyhost=true&application=dubbo_server&dubbo=2.5.3&interface=com.qigang.dubbo.service.DemoService&methods=sayHello&pid=21536&side=provider&timestamp=1581222394366

    //如果URL中没有参数scope=remote则首先本地暴露服务,也就是说没有在xml的<dubbo:service>节点配置了scope=remote参数
    //本地暴露的时候复制原始的URL并进行一个修改,例如改成:
    //injvm://127.0.0.1/com.qigang.dubbo.service.DemoService?anyhost=true&application=dubbo_server&dubbo=2.5.3&interface=com.qigang.dubbo.service.DemoService&methods=sayHello&pid=22380&side=provider&timestamp=1581223246560
    //本地暴露的协议是injvm
    ServiceConfig.exportLocal()

    //在这里注册到注册中心
    RegistryProtocol.export()
    //注册之后暴露服务
    DubboProtocol.export()
        DubboProtocol.openServer()

        //*******************************************
        private void openServer(URL url) {
            // find server.
            String key = url.getAddress();
            //client can export a service which's only for server to invoke
            boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
            if (isServer) {
                ExchangeServer server = serverMap.get(key);
                if (server == null) {
                    serverMap.put(key, createServer(url));
                } else {
                    // server supports reset, use together with override
                    server.reset(url);
                }
            }
        }
        //*******************************************

        DubboProtocol.createServer()
        HeaderExchanger.connect()
        NettyTransporter.connect()
        NettyClient构造函数
        AbstractClient构造函数
        NettyClient.doOpen()

3. 服务消费

服务消费主要是通过ReferenceBean来实现的,这个类也实现了InitializingBean接口

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

	private static final long serialVersionUID = 213195494150089726L;
	
	private transient ApplicationContext applicationContext;

	public ReferenceBean() {
        super();
    }

    public ReferenceBean(Reference reference) {
        super(reference);
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		SpringExtensionFactory.addApplicationContext(applicationContext);
	}
    
    public Object getObject() throws Exception {
        return get();
    }

    public Class<?> getObjectType() {
        return getInterfaceClass();
    }

    @Parameter(excluded = true)
    public boolean isSingleton() {
        return true;
    }

    @SuppressWarnings({ "unchecked"})
    public void afterPropertiesSet() throws Exception {
        //...
    }
}
ReferenceBean.afterPropertiesSet()
ReferenceBean.getObject()
ReferenceConfig.get()
ReferenceConfig.init()
ReferenceConfig.createProxy()
    JavassistProxyFactory.getInvoker()

参考

dubbo官方文档

dubbo系列

Dubbo SPI机制详解

【Dubbo源码阅读系列】服务暴露之本地暴露