Dubbo的SPI机制

SPI机制类似于Spring的IOC容器,实现对bean的管理。框架本身定义接口、规范,第三方只需要将自己实现在META-INF下描述清楚,那么框架就会自动加载你的实现。Dubbo的规则是在META-INF/dubbo,META-INF/dubbo/internal或者META-INF/services创建描述文件,以Property的形式对所实现的接口进行描述,比如dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol,就给出了Protocol接口的一个实现类,然后框架自动家在这个类。

Dubbo对这一块的实现全部都集中在类ExtensionLoader中,那么接下来将围绕这个类来介绍Dubbo插件化的实现,在介绍Dubbo插件化实施之前,需要知道Dubbo框架是以URL为总线的模式,即运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。

Dubbo中主要有两种SPI的实现,一种是Adaptive,另一种是Activate。

Adaptive

想要使用dubbo的SPI机制,所定义接口必须首先标注@SPI注解,否则dubbo不会加载其实现类。dubbo提供两种方式的Adaptive,一种是对某个接口实现对应的适配器,另一种是dubbo框架动态生成类。第一种实现方式可以参考ExtensionFactory这个类,其共有三种不同的实现,其中AdaptiveExtensionFactory标注了@Adaptive注解,dubbo只会加载这个类,然后这个类会决定具体调用SpringExtensionFactory还是SpiExtensionFactory,这种方式相对比较简单,也比较容易理解。不过需要注意的是,使用这种方法,只允许其中一个实现类标注@Adaptive注解,否则dubbo会报异常。 另一种方式是dubbo框架动态生成类,ExtensionLoader通过分析接口配置的Adaptive注解的规则,加载合适的实现类。下面以Transporter接口为例来说明,其源代码如下:

@SPI("netty")
public interface Transporter {  
    /**
     * Bind a server.
     * 
     * @see com.alibaba.dubbo.remoting.Transporters#bind(URL, Receiver, ChannelHandler)
     * @param url server url
     * @param handler
     * @return server
     * @throws RemotingException 
     */
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
    /**
     * Connect to a server.
     * 
     * @see com.alibaba.dubbo.remoting.Transporters#connect(URL, Receiver, ChannelListener)
     * @param url server url
     * @param handler
     * @return client
     * @throws RemotingException 
     */
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

ExtensionLoader会根据上述Adaptive配置的值来生成如下代码:

package com.alibaba.dubbo.remoting;  
import com.alibaba.dubbo.common.extension.ExtensionLoader;  
public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter{  
  public com.alibaba.dubbo.remoting.Client connect(
    com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) 
    throws com.alibaba.dubbo.remoting.RemotingException {
    if (arg0 == null) 
      throw new IllegalArgumentException("url == null");
    com.alibaba.dubbo.common.URL url = arg0;
    String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
    if(extName == null) 
      throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url (" + url.toString() + ") use keys([client, transporter])");
    com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
      (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
    return extension.connect(arg0, arg1);
  }
  public com.alibaba.dubbo.remoting.Server bind(
    com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) 
    throws com.alibaba.dubbo.remoting.RemotingException {
    if (arg0 == null) 
      throw new IllegalArgumentException("url == null");
    com.alibaba.dubbo.common.URL url = arg0;
    String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
    if(extName == null) 
      throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url (" + url.toString() + ") use keys([server, transporter])");
    com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
      (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
    return extension.bind(arg0, arg1);
  }
}

以bind方法为例来说明。上面已经提到,URL在Dubbo中非常重要,在这里又有体现。dubbo会根据@Adaptive注解中配置的value,依次调用URL的getParameter方法来拿到对应的extName,如果都没有,则使用默认名字作为extName,然后根据extName去找对应的Transporter接口的实现类,以此来实现动态加载接口的不同实现类。

Activate

待补充

总体流程图

dubbo-spi-extensionloader

参考:从ExtensionLoader看Dubbo插件化

Shaohang Zhao

Read more posts by this author.