Dubbo SPI 机制了解

参考

官方文档:Dubbo可扩展机制实战 Dubbo可扩展机制源码解析

Java SPI

在dubbo 官方文档中,我们可以看到Java SPI的实践 的一个例子,非常的简单明了的了解到如何根据配置得到具体的实现这,譬如JDBC的实现;Java SPI(Service Provider Interface)是JDK内置的一种动态加载扩展点的实现。在ClassPath的META-INF/services目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。JDK中使用java.util.ServiceLoader来加载具体的实现;
贴一篇讲解SPI写的还不错的一篇文档,内容比较的充实:Java的SPI机制分析
引用那里的描述:
Java SPI 思想分析:
(1)当我们的系统里面抽象的各个模块,往往有很多不同的实现方案,比如日志处理模块、xml解析模块、过滤器的模块等,一般我们模块之间是基于接口编程的,模块之间不会对具体实现类进行硬编码。一旦代码里涉及到具体实现类时,就违反了可插拔的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。Java SPI 就是提供了这样一种机制:为某个接口寻找服务实现的机制。
(2)spi规范约定:
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文 件里就是实现该服务接口的具体实现类。而当外部程序装配这个
模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的 实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
(3)使用场景:
a、jdbc
b、common-logging
c、Dubbo等

官方文档中也有讲解为啥要放弃Java的SPI:
Java SPI的使用很简单。也做到了基本的加载扩展点的功能。但Java SPI有以下的不足:

  • 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。
  • 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
  • 扩展如果依赖其他的扩展,做不到自动注入和装配
  • 不提供类似于Spring的IOC和AOP功能
  • 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持

所以Java SPI应付一些简单的场景是可以的,但对于Dubbo,它的功能还是比较弱的。Dubbo对原生SPI机制进行了一些扩展。

Dubbo SPI 中的概念

1、扩展点(Extension Point)

是一个Java的接口,由于引入SPI的原因就是为了面向接口编程,可以自由的添加其他的扩展的实现哦,这个接口必须被标注@SPI ,可以标注默认的选择,这些默认的选择是一个名称,根据接口对应的文件夹中key=value哦
Dubbo-Dubbo SPI 机制了解_apache

threadlocal=org.apache.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=org.apache.dubbo.cache.support.lru.LruCacheFactory
jcache=org.apache.dubbo.cache.support.jcache.JCacheFactory
expiring=org.apache.dubbo.cache.support.expiring.ExpiringCacheFactory
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * default extension name 默认的扩展实现类的名称(这个会在)
     */
    String value() default "";

}

比如这个缓存接口就是一个扩展

/**
 * Interface needs to be implemented by all the cache store provider.Along with implementing <b>CacheFactory</b> interface
 * entry needs to be added in org.apache.dubbo.cache.CacheFactory file in a classpath META-INF sub directories.
 *
 * @see Cache
 */
@SPI("lru")
public interface CacheFactory {

    /**
     * CacheFactory implementation class needs to implement this return underlying cache instance for method against
     * url and invocation.
     * @param url
     * @param invocation
     * @return Instance of Cache containing cached value against method url and invocation.
     */
    @Adaptive("cache")
    Cache getCache(URL url, Invocation invocation);

}

2、扩展(Extension)

扩展点的实现类,比如缓存工厂中的实现类,有好几个呢!默认的是lru

3、扩展实例(Extension Instance)

就是上面的扩展的实现的实例

4、扩展自适应实例(Extension Adaptive Instance)

这个东西不好理解,首先第一点,这个和扩展实例是一样的,都是实现了扩展的接口,但是这个自适应扩展实例有些不同,这个自适应扩展实例的作用是,根据当前调用的信息,从扩展实例中找到最合适的一个扩展实例进行调用,有点像代理的意思,一般这种自适应扩展实例都会被 Adaptive这个注解标识!主要的处理意图就是为了能够动态的选择扩展的实例的信息。这里需要说明,如果没有实现自适应的扩展类,dubbo会自动的给你实现一个,然后根据URL这个参数中的信息去选择一个合适的扩展的实现类的信息。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    /**
     * Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
     * in the URL, and the parameter names are given by this method.
     * <p>
     * If the specified parameters are not found from {@link URL}, then the default extension will be used for
     * dependency injection (specified in its interface's {@link SPI}).
     * <p>
     * For examples, given <code>String[] {"key1", "key2"}</code>:
     * <ol>
     * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
     * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
     * <li>use default extension if 'key2' doesn't appear either</li>
     * <li>otherwise, throw {@link IllegalStateException}</li>
     * </ol>
     * If default extension's name is not give on interface's {@link SPI}, then a name is generated from interface's
     * class name with the rule: divide classname from capital char into several parts, and separate the parts with
     * dot '.', for example: for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, its default name is
     * <code>String[] {"yyy.invoker.wrapper"}</code>. This name will be used to search for parameter from URL.
     *
     * @return parameter key names in URL
     */
    String[] value() default {};

}

@Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展。 @Adaptive注解用在类上代表实现一个装饰类,类似于设计模式中的装饰模式,它主要作用是返回指定类,目前在整个系统中AdaptiveCompiler、AdaptiveExtensionFactory这两个类拥有该注解。

5、 ExtentionLoader

维护加载SPI接口的信息,可以获取到对应的自适应扩展的实例或者自适应的实例的信息

6、Activate详解

这个其实就是对于各种扩展实现的一个过滤操作,只选择需要的一个部分,ExtensionLoader中提供了这样的选择方法,在对于生产者消费者不同进行过滤选择不同的过滤器有点效果哦,具体看博客:Dubbo SPI之Activate详解

还有一些其他的,官方文档讲解的非常的详细。