dubbo spi

184 阅读4分钟

是什么?

spi类似拦截器,就是开发者需要扩展开发功能实现类。

虽然有点类似拦截器,但是spi其实是包含了拦截器,因为自定义拦截器也是spi的一种。但是拦截器往往是对所有请求都生效的一个公共功能,而spi不一定是拦截所有的请求。

举例子

拦截器

是对所有请求生效,统一实现什么功能。

spi

jdbc api的各个数据库厂商的具体实现类。

dubbo所有组件都是spi,有自带的实现类,但是对外开放实现

具体有哪些spi?

全部都是spi。

spi和api的区别?

api是给客户端调用

api是给提供者提供了api接口,给消费者/客户端调用。

并且,实现类也是提供者实现的。

而,spi是消费者提供了接口,然后开发者去实现接口,自定义实现类。最好的两个例子就是,第一个是java jdbc api的各个不同数据库厂商去实现驱动类。第二个是,spring boot框架的slf日志接口类,至于具体实现和使用哪个实现类,既可以是默认的logback,也可以是log4j。

spi是给开发者扩展

其实本质就是开发者自定义实现类,比如工作当中自定义了dubbo日志拦截器。

java也有spi

缺点

不能按需加载,只能加载所有的实现类,哪怕不需要某个实现类。

实现步骤

1、jar

2、jar里的配置文件

指定具体的实现类。

实现原理

反射,基于接口,创建实现类——全部都是反射。

可见反射,有多么重要——注入数据,拦截器,api,spi,底层全部都是基于反射来创建类。

不懂反射,就不可能懂底层实现原理。

最好的例子-各个数据库厂商的jdbc驱动类jar

jdk只提供驱动类接口。

具体实现,用哪个实现类,看你用哪个数据库厂商的jar,即使用哪个实现类。

在哪里看实现类?

jar的配置文件里。

demo

xie.infoq.cn/article/524…

和java spi的区别?dubbo为什么要自己实现spi?

就是为了解决java spi的缺点,现在可以按需加载实现类。

实现原理

参考jdk spi,有点不一样——其实就是用法有点不一样,即可以按需加载实现类。但是实质上没有任何区别,底层都是基于反射,创建具体实现类对象。

工作实践

自定义dubbo日志拦截器,实现在调用dubbo接口的时候,自动打印入参和出参,无需手动打印日志。

实现步骤

  1. 自定义拦截器类

实现dubbo Filter接口

/**
 * dubbo日志拦截器,作用:
 1.打印消费者和提供者的入参和响应 基于dubbo拦截器实现 
 2.全链路追踪 基于日志拦截器(线程本地变量)和dubbo上下文实现。(注:全链路追踪的每个服务,都要配置该拦截器,才能实现全链路追踪)
 */
@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class AccessLogExtFilter implements Filter {

dubbo Filter接口

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.SPI;

/**
 * Filter. (SPI, Singleton, ThreadSafe)
 */
@SPI
public interface Filter {

    /**
     * do invoke filter.
     * <p>
     * <code>
     * // before filter
     * Result result = invoker.invoke(invocation);
     * // after filter
     * return result;
     * </code>
     *
     * @param invoker    service
     * @param invocation invocation.
     * @return invoke result.
     * @throws RpcException
     * @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation)
     */
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;

}
  1. 配置

在META_INF目录指定具体实现类

accessLogExtFilter=xxx.common.filter.dubbo.AccessLogExtFilter

只需要按规定格式在指定目录,添加指定配置文件,然后以key/value的形式配置spi实现类即可,只需要这里配置之后,就无需任何配置,就自动生效了,因为dubbo会加载这里的spi实现类。


dubbo其实是已经自带了日志拦截器实现类,只不过不能满足需求,所以才扩展和自定义实现类。

默认日志实现类,是没有开启的。

阿里druid连接池

不仅仅是dubbo框架,其他框架基本上都是这个套路,就是如果要扩展的话,即实现自定义类,就实现拦截器,然后配置即可。基本上就是这2个步骤。

比如,阿里druid连接池,我们想要实现监控连接池状态的功能,具体功能如下

  1. 获取数据库连接耗时
  2. 活跃连接数量占连接池总数量的比例是否超过阈值

然后,上面2个监控指标,如果超过阈值就告警。


实现步骤

这里只讲spi实现和配置,监控和告警是基于美团cat实现的,和spi无关,这里不讲。

  1. 自定义实现类

实现druid的拦截器接口

/**
 * 基于druid拦截器实现,本质是重写获取数据库连接的方法,从而实现监控功能
 * @author gzh
 * @createTime 2020/10/14 10:11 AM
 */
public class DruidDataSourceFilter extends FilterAdapter {

druid拦截器接口

package com.alibaba.druid.filter;

public abstract class FilterAdapter extends NotificationBroadcasterSupport implements Filter {
  1. 配置

指定实现类

druid.filters.cat=xxx.common.monitor.druid.DruidDataSourceFilter

然后,还需要再多一个配置,即在连接池配置里指定自定义拦截器名字cat

<bean id="dataSource_trade" class="xxx.trade.sys.init.DruidSecretBasicDataSource" destroy-method="close">
 <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
 <property name="url"     value="${orderTrade.url}" />
 <property name="username" value="${orderTrade.username}" />
 <property name="password" value="${orderTrade.password}" />
 <property name="defaultAutoCommit" value="false"></property>
 <property name="initialSize" value="${orderTrade.inisize}"/>
 <property name="maxActive" value="${orderTrade.maxsize}"/> 
 <property name="minIdle" value="${orderTrade.minidle}" />
 <property name="maxWait" value="${orderTrade.maxWait}"/>
 <!-- 验证连接是否可用,使用的SQL语句 -->
 <property name="validationQuery" value="select * from dual"/>
 <!-- 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 -->
 <property name="testWhileIdle" value="true" />
 <!-- 借出连接时不要测试,否则很影响性能 默认为true-->
 <property name="testOnBorrow" value="false"/>
 <!-- 每60秒运行一次空闲连接回收器 -->
 <property name="timeBetweenEvictionRunsMillis" value="60000" />
 <!-- 池中的连接空闲30分钟后被回收,默认值就是30分钟 -->
 <property name="minEvictableIdleTimeMillis" value="1800000"/>
 <property name="filters" value="mergeStat,wall,cat" /> //指定自定义拦截器实现类名字cat
</bean>

参考

dubbo.apache.org/zh/docsv2.7…