|
| 1 | +# 分布式服务框架之Dubbo(SPI机制) |
| 2 | + |
| 3 | +### 什么是SPI机制? |
| 4 | +##### Java SPI简介 |
| 5 | +在了解Dubbo的SPI机制之前,我们先了解下Java提供的SPI(service provider interface)机制,SPI是JDK内置的一种服务提供发现机制。目前市面上很多框架都用它来做服务的扩展发现。简单说它是一种动态替换发现的机制。 |
| 6 | + |
| 7 | +举个简单的例子,我们想在运行时动态添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。大家耳熟能详的如JDBC,日志框架都有用到。 |
| 8 | + |
| 9 | +##### 实现Java SPI需要遵循的标准 |
| 10 | +实现Java SPI需要满足如下标准: |
| 11 | +1. 需要在classpath下创建一个目录,该目录名必须是:META-INF/service; |
| 12 | +2. 在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 : |
| 13 | + 2.1 文件名必须是扩展的接口的全路径名称; |
| 14 | + 2.2 文件内部描述该扩展接口的所有实现类; |
| 15 | + 2.3 文件的编码格式是UTF-8; |
| 16 | +3. 通过 java.util.ServiceLoader 的加载机制来发现; |
| 17 | + |
| 18 | +##### SPI的应用场景 |
| 19 | +- 这里我们以JDBC驱动为例:JDK本身提供了数据访问的api,在java.sql这个包里,我们在连接数据的时候会用到java.sql.Driver驱动接口。但是这个java.sql.Driver驱动接口在JDK本省并没有任何实现,而是提供了一套标准的api接口。 |
| 20 | + |
| 21 | +```java |
| 22 | +package java.sql; |
| 23 | +import java.util.logging.Logger; |
| 24 | + |
| 25 | +public interface Driver { |
| 26 | + Connection connect(String url, java.util.Properties info) |
| 27 | + throws SQLException; |
| 28 | + boolean acceptsURL(String url) throws SQLException; |
| 29 | + DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) |
| 30 | + throws SQLException; |
| 31 | + int getMajorVersion(); |
| 32 | + int getMinorVersion(); |
| 33 | + boolean jdbcCompliant(); |
| 34 | + public Logger getParentLogger() throws SQLFeatureNotSupportedException; |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +- 这里我们以mysql的JDBC驱动接口实现:mysql驱动也是通过SPI机制把java.sql.Driver和mysql驱动做了集成,这样就达到了各个数据库厂商自己去实现数据连接,JDK不关系你是怎么实现的。 |
| 39 | + |
| 40 | +<img src="https://ipman-blog-1304583208.cos.ap-nanjing.myqcloud.com/dubbo/1041608699463_.pic.jpg" width = "740" height = "175" alt="图片名称" align=center /> |
| 41 | + |
| 42 | +##### SPI的缺点 |
| 43 | +- JDK标准的SPI会一次性加载实例化扩展点的所有实现。如果存在一些SPI的实现类并没有用到,那么初始化相对来说比较耗时; |
| 44 | +- 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位; |
| 45 | + |
| 46 | +### Dubbo中的SPI机制? |
| 47 | +Dubbo SPI不是通过JDK的SPI机制实现的,而是自己实现了一套自己的SPI标准,Dubbo通过自身提供的@SPI注解声明扩展API,并提供默认的实现类。我们通过@Adaptive可以自动激活扩展点,替换Dubbo默认的实现类。 |
| 48 | + |
| 49 | +### 在Dubbo中有哪些功能能实现SPI扩展点? |
| 50 | +<img src="https://ipman-blog-1304583208.cos.ap-nanjing.myqcloud.com/dubbo/1001608375467_.pic.jpg" width = "700" height = "640" alt="图片名称" align=center /> |
| 51 | + |
| 52 | +Dubbo各层说明,除了Service和Config层为API,其它各层均为SPI |
| 53 | +> - 第一层:service层,接口层,给服务提供者和消费者来实现的; |
| 54 | +> - 第二层:config层,配置层,主要是对dubbo进行各种配置的; |
| 55 | +> - 第三层:proxy层,服务接口透明代理,生成服务的客户端Stub和服务端Skeleton; |
| 56 | +> - 第四层:registry层,服务注册层,负责服务的注册和发现; |
| 57 | +> - 第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务; |
| 58 | +> - 第六层:monitor层,监控层,对rpc接口的次数和调用时间进行监控; |
| 59 | +> - 第七层:protocol层,远程调用曾,分装rpc调用; |
| 60 | +> - 第八层:exchange层,信息交换层,分装请求相应模式,同步转异步; |
| 61 | +> - 第九层:transport层,网络传输层,抽象mina和netty为统一接口; |
| 62 | +> - 第十层:serialize层,数据序列化层,网络传输需要; |
| 63 | +
|
| 64 | +### Dubbo SPI实战(以Consumer端的Filter为例) |
| 65 | +##### 1.实现Dubbo SPI需要遵循的标准 |
| 66 | +目录结构: |
| 67 | +```java |
| 68 | +src |
| 69 | + |-main |
| 70 | + |-java |
| 71 | + |-com |
| 72 | + |-xxx |
| 73 | + |-XxxFilter.java (实现Filter接口) |
| 74 | + |-resources |
| 75 | + |-META-INF |
| 76 | + |-dubbo |
| 77 | + |-org.apache.dubbo.rpc.Filter (纯文本文件,内容为:xxx=com.xxx.XxxFilter) |
| 78 | +``` |
| 79 | + |
| 80 | + |
| 81 | +##### 2.实现Dubbo SPI扩展点Filter接口 |
| 82 | +需要注意的是Dubbo中的Filter分Consumer端和Provider端,可以通过@Activate配置过滤条件,比如gourp过滤和key过滤等; |
| 83 | +```java |
| 84 | +@Activate(group = CommonConstants.CONSUMER) //用于表示当前SPI扩展只在客户端被激活 |
| 85 | +public class CustomConsumerFilter implements Filter, Filter.Listener { |
| 86 | + |
| 87 | + //[ CustomConsumerFilter invoke ] |
| 88 | + // service=com.ipman.dubbo.spi.sample.api.DemoService, |
| 89 | + // method=sayHello, |
| 90 | + // args=["ipman"] |
| 91 | + @Override |
| 92 | + public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { |
| 93 | + System.out.println("[ CustomConsumerFilter invoke ] service=" + invocation.getServiceName() |
| 94 | + + ",method=" + invocation.getMethodName() + ",args=" + JSON.toJSONString(invocation.getArguments())); |
| 95 | + if (("sayHello").equals(invocation.getMethodName())) { |
| 96 | + //throw new RpcException("You do not have access to this method"); |
| 97 | + } |
| 98 | + return invoker.invoke(invocation); |
| 99 | + } |
| 100 | + |
| 101 | + //[ CustomConsumerFilter onResponse ] |
| 102 | + // service=com.ipman.dubbo.spi.sample.api.DemoService,method=sayHello, |
| 103 | + // args=["ipman"], |
| 104 | + // response="Hello ipman, response from provider: 10.13.224.253:20880" |
| 105 | + @Override |
| 106 | + public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) { |
| 107 | + System.out.println("[ CustomConsumerFilter onResponse ] service=" + invocation.getServiceName() |
| 108 | + + ",method=" + invocation.getMethodName() + ",args=" + JSON.toJSONString(invocation.getArguments()) |
| 109 | + + ",response=" + JSON.toJSONString(appResponse.getValue())); |
| 110 | + } |
| 111 | + |
| 112 | + @Override |
| 113 | + public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) { |
| 114 | + System.out.println("[ CustomConsumerFilter onError ] service=" + invocation.getServiceName() |
| 115 | + + ",method=" + invocation.getMethodName() + ",args=" + JSON.toJSONString(invocation.getArguments()) |
| 116 | + + ",error=" + t.getMessage()); |
| 117 | + } |
| 118 | +} |
| 119 | +``` |
| 120 | +##### 3.配置META-INF.dubbo.org.apache.dubbo.rpc.Filter文件 |
| 121 | +```java |
| 122 | +consumerFilter=com.ipman.dubbo.spi.sample.spi.CustomConsumerFilter |
| 123 | +``` |
| 124 | + |
| 125 | + |
0 commit comments