先来了解一下java中的spi机制
jdk中的spi
SPI简介
一种策略模式,通过在META-INF/services/包下定义接口命名的文件,来决定使用哪个实现
调用过程
public class Main {
public static void main(String[] args) {
System.out.println("---加载接口--");
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(Log.class);
Iterator<SpiService > iterator = serviceLoader.iterator();
//上面只声明了两个接口,所以这里会分别调用rmi和rest的实现类的sayhello方法
while (iterator.hasNext()) {
SpiService s1= iterator.next();
s1.sayHello
}
System.out.println("---运行结束---");
}
}
spi 全称为(Service Provider Interface),是JDK内置的一种服务提供机制。
这个是针对厂商或者插件的。
一般来说对于未知的实现或者对扩展开放的系统,通常会把一些东西抽象出来,抽象的各个模块往往有很多不同的实现方案,例如:日志模块、xml解析模块、jdbc模块等。
SPI约定
当服务的提供者,提供了服务接口的一种实现之后,在jar包中META-INF/services目录里同时创建一个以服务接口命名的文件。
该文件里就是实现该服务接口的具体实现类(全称)。
而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
在日志实现中可以使用
dubbo中的spi实现
dubbo中的spi实现更加人性化,可以支持自定义标签中的指定属性决定实现类的读取
比如这个如在均衡的策略
dubbo中的api的使用流程
凡是dubbo中, 接口上有 @SPI标注的,都表明此接口支持扩展,能以标签中属性的方式进行配置扩展
类似这个负载的接口
@SPI("random")
public interface LoadBalance {
@Adaptive({"loadbalance"})
<T> Invoker<T> select(List<Invoker<T>> var1, URL var2, Invocation var3) throws RpcException;
}
下面我们自己来写一个负载的扩展类
1、引入jar依赖
<!-- dubbo支持 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.7</version>
<scope>provided</scope>
</dependency>
2、编写实现类
3、resource下新建类全路径名文件,为每个实现分配一个key
4、在消费端,便可使用上面步骤中定的实现策略(以key指代)
dubbo中SPI机制源码解读
在运行的时候会通过一个叫做ExtensionLoader的加载器来进行dubbo的扩展点加载
拿消费端的Reference标签举例,
通过看dubbo解析自定义标签的源码
this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
我们知道dubbo是将reference标签的内容封装到ReferenceBean里的,打开ReferenceBean
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {}
打开父类ReferenceConfig,可以看到创建代理对象时的这一句核心代码
this.invoker = refprotocol.refer(this.interfaceClass, (URL)this.urls.get(0));
而这个refprotocol则是个ReferenceConfig的静态变量
(也许您会好奇,每一个reference标签对应一个ReferenceConfig对象,那在这里的协议使用类变量,会不会导致每个对象只有一个协议呢?这里就牵扯到dubbo的ExtensionLoader加载器了)
//getExtensionLoader获取加载器 getAdaptiveExtension获取代理类
private static final Protocol refprotocol = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
getExtensionLoader(Protocol.class)为protocol接口生成一个加载器
getAdaptiveExtension(),使用加载器生成一个代理对象---- protocol接口对象
代理对象执行时,根据参数(扩展名extName)选择实际对象
最后的效果:
每个接口扩展点----- 对应一个ExtensionLoader加载器,内部是一个currenthashmap 如:
protocol -------------- ExtensionLoader实例< protocol>
filter -------------- ExtensionLoader实例< filter >
loadbalance -------------- ExtensionLoader实例< loadbalance >
代理对象是实时生成的,包括class文件,当然只生成接口中有@Adaptive注解的方法
a、dubbo启动加载实现类时,以 key-实例 方式map缓存各个实现类
b、实际调用时,通过key --取实现需要那个实现
c、调用的发生,由生成的代理对象的来发起,最终是从URL总线中,找出extName值,
extName做为key,在缓存map中取出正确的实现实现类