代码改变世界

从ExtensionLoader理解Dubbo扩展机制

2017-01-22 15:39  louistz  阅读(471)  评论(0编辑  收藏  举报

Dubbo的扩展机制是怎么实现的?最简单的回答就是@SPI. Dubbo的插件化思路来源于Java SPI.
 
JAVA SPI 机制
    SPI的全名为Service Provider Interface. 大多数人可能不了解,因为它是JDK针对插件或者厂商的。java spi机制的思想就是:
我们的系统的抽象模块(接口),往往有很多不同方案的实现。如日志模块,jdbc模块等。而在面向对象的设计里,我们一般都要做模块解耦,面向接口编程。但如果要切换接口的不同实现,就可能需要改动代码。为了实现模块的自动装配而不硬编码,就需要一种服务发现机制,这就有了java spi.
    java spi 约定:当我们实现了一个接口,在jar包的 META-INF/services 目录下放一个以该接口命名的文件,文件的内容是 该接口的具体实现类。当外部程序装配jar包时,就通过这个文件找到具体的实现类,完成模块的注入。而这个服务发现的过程就用的是 java.util.ServiceLoader.
 
JAVA SPI 举例
接口:
package com.my.spi; 
public interface Search {  
   public List serch(String keyword);  
} 
实现:
        A的实现:com.A.spi.impl.FileSearch
        B的实现:  com.B.spi.impl.DBSearch
文件:
        A- jar包 META-INF/services目录下文件com.my.spi.Search 内容为:com.A.spi.impl.FileSearch
        B- jar包 META-INF/services目录下文件com.my.spi.Search 内容为:com.B.spi.impl.DBSearch
使用:    
package com.xyz.factory;  
import java.util.Iterator;  
import java.util.ServiceLoader;  
import my.xyz.spi.Search;  
public class SearchFactory {  
    private SearchFactory() {  
    }  
    public static Search newSearch() {  
        Search search = null;  
        ServiceLoader<Search> serviceLoader = ServiceLoader.load(Search.class);  
        Iterator<Search> searchs = serviceLoader.iterator();  
        if (searchs.hasNext()) {  
            search = searchs.next();  
        }  
        return search;  
    }  
}  

 

  1. package my.xyz.test;  
    import java.util.Iterator;  
    import java.util.ServiceLoader;  
    import com.xyz.factory.SearchFactory;  
    import my.xyz.spi.Search;  
    public class SearchTest {  
        public static void main(String[] args) {  
            Search search = SearchFactory.newSearch();  
            search.serch("java spi test");  
        }  
    }  

 

Dubbo的扩展点机制:ExtensionLoader和SPI
Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。

Dubbo改进了JDK标准的SPI的以下问题:

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
  • 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

Dubbo提供了一个@SPI  扩展注解,它达到的功能和java的spi一样,不过它的服务发现用的不是 ServiceLoader,而是自己实现的ExtensionLoader.

约定:

在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

(注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)

扩展Dubbo的协议示例:

在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

xxx=com.alibaba.xxx.XxxProtocol

实现类内容:

package com.alibaba.xxx;
 
import com.alibaba.dubbo.rpc.Protocol;
 
public class XxxProtocol implemenets Protocol {
 
    // ...
 
}

注意: 扩展点使用单一实例加载(请确保扩展实现的线程安全性),Cache在ExtensionLoader中。  

ExtensionLoader的实现

ExtensionLoader 通过其loadFile方法,加载 META-INF/internal/ ,MEAT-INF/dubbo/,META-INF/services/目录下的配置文件(接口全名为文件名),将扩展类加载并缓存。

  1. @SPI注解用在接口上, 来表明接口实现是可扩展的。这种标记有@SPI的接口,就是可扩展点
  2. 在dubbo中,扩展点默认存在三种实现: Adaptive, Wrapped ,以及normal实现。 其中normal实现就是第三方实现的有特定功能的接口实现。 Adaptive和Wrapped都是对这些normal实现的适配和包装。 Adaptive是某种意义上的集成和适配。Wrapped是包装和代理,有些aop的意思。可以在dubbo源码中找几个默认的adaptive和wrapped实现看看源码。
  3. 其中扩展点实现类还支持@Activate注解,指明了扩展点实现类被激活的加载条件。 

 getAdaptiveExtensionClass流程:

1.先找缓存

2.缓存没有,loadExtension From file (只能有一个实现类被@Adaptive标注)

3. loadFile后还是没有,动态生成(Xxx$Adaptive implements Xxx)。(接口方法至少有一个标注了@Adaptive,且只动态生成有标记的方法)