在了解Dubbo的SPI机制之前,我们先了解下Java提供的SPI(service provider interface)机制,SPI是JDK内置的一种服务提供发现机制。目前市面上很多框架都用它来做服务的扩展发现。简单说它是一种动态替换发现的机制。
举个简单的例子,我们想在运行时动态添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。大家耳熟能详的如JDBC,日志框架都有用到。
实现Java SPI需要满足如下标准:
- 需要在classpath下创建一个目录,该目录名必须是:META-INF/service;
- 在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 : 2.1 文件名必须是扩展的接口的全路径名称; 2.2 文件内部描述该扩展接口的所有实现类; 2.3 文件的编码格式是UTF-8;
- 通过 java.util.ServiceLoader 的加载机制来发现;
- 这里我们以JDBC驱动为例:JDK本身提供了数据访问的api,在java.sql这个包里,我们在连接数据的时候会用到java.sql.Driver驱动接口。但是这个java.sql.Driver驱动接口在JDK本省并没有任何实现,而是提供了一套标准的api接口。
package java.sql;
import java.util.logging.Logger;
public interface Driver {
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
- 这里我们以mysql的JDBC驱动接口实现:mysql驱动也是通过SPI机制把java.sql.Driver和mysql驱动做了集成,这样就达到了各个数据库厂商自己去实现数据连接,JDK不关系你是怎么实现的。
- JDK标准的SPI会一次性加载实例化扩展点的所有实现。如果存在一些SPI的实现类并没有用到,那么初始化相对来说比较耗时;
- 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位;
Dubbo SPI不是通过JDK的SPI机制实现的,而是自己实现了一套自己的SPI标准,Dubbo通过自身提供的@SPI注解声明扩展API,并提供默认的实现类。我们通过@Adaptive可以自动激活扩展点,替换Dubbo默认的实现类。
Dubbo各层说明,除了Service和Config层为API,其它各层均为SPI
- 第一层:service层,接口层,给服务提供者和消费者来实现的;
- 第二层:config层,配置层,主要是对dubbo进行各种配置的;
- 第三层:proxy层,服务接口透明代理,生成服务的客户端Stub和服务端Skeleton;
- 第四层:registry层,服务注册层,负责服务的注册和发现;
- 第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务;
- 第六层:monitor层,监控层,对rpc接口的次数和调用时间进行监控;
- 第七层:protocol层,远程调用曾,分装rpc调用;
- 第八层:exchange层,信息交换层,分装请求相应模式,同步转异步;
- 第九层:transport层,网络传输层,抽象mina和netty为统一接口;
- 第十层:serialize层,数据序列化层,网络传输需要;
目录结构:
src
|-main
|-java
|-com
|-xxx
|-XxxFilter.java (实现Filter接口)
|-resources
|-META-INF
|-dubbo
|-org.apache.dubbo.rpc.Filter (纯文本文件,内容为:xxx=com.xxx.XxxFilter)
需要注意的是Dubbo中的Filter分Consumer端和Provider端,可以通过@Activate配置过滤条件,比如gourp过滤和key过滤等;
@Activate(group = CommonConstants.CONSUMER) //用于表示当前SPI扩展只在客户端被激活
public class CustomConsumerFilter implements Filter, Filter.Listener {
//[ CustomConsumerFilter invoke ]
// service=com.ipman.dubbo.spi.sample.api.DemoService,
// method=sayHello,
// args=["ipman"]
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println("[ CustomConsumerFilter invoke ] service=" + invocation.getServiceName()
+ ",method=" + invocation.getMethodName() + ",args=" + JSON.toJSONString(invocation.getArguments()));
if (("sayHello").equals(invocation.getMethodName())) {
//throw new RpcException("You do not have access to this method");
}
return invoker.invoke(invocation);
}
//[ CustomConsumerFilter onResponse ]
// service=com.ipman.dubbo.spi.sample.api.DemoService,method=sayHello,
// args=["ipman"],
// response="Hello ipman, response from provider: 10.13.224.253:20880"
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
System.out.println("[ CustomConsumerFilter onResponse ] service=" + invocation.getServiceName()
+ ",method=" + invocation.getMethodName() + ",args=" + JSON.toJSONString(invocation.getArguments())
+ ",response=" + JSON.toJSONString(appResponse.getValue()));
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
System.out.println("[ CustomConsumerFilter onError ] service=" + invocation.getServiceName()
+ ",method=" + invocation.getMethodName() + ",args=" + JSON.toJSONString(invocation.getArguments())
+ ",error=" + t.getMessage());
}
}
consumerFilter=com.ipman.dubbo.spi.sample.spi.CustomConsumerFilter