参考
官方文档: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哦
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详解
还有一些其他的,官方文档讲解的非常的详细。