是什么?
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
和java spi的区别?dubbo为什么要自己实现spi?
就是为了解决java spi的缺点,现在可以按需加载实现类。
实现原理
参考jdk spi,有点不一样——其实就是用法有点不一样,即可以按需加载实现类。但是实质上没有任何区别,底层都是基于反射,创建具体实现类对象。
工作实践
自定义dubbo日志拦截器,实现在调用dubbo接口的时候,自动打印入参和出参,无需手动打印日志。
实现步骤
- 自定义拦截器类
实现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;
}
- 配置
在META_INF目录指定具体实现类
accessLogExtFilter=xxx.common.filter.dubbo.AccessLogExtFilter
只需要按规定格式在指定目录,添加指定配置文件,然后以key/value的形式配置spi实现类即可,只需要这里配置之后,就无需任何配置,就自动生效了,因为dubbo会加载这里的spi实现类。
dubbo其实是已经自带了日志拦截器实现类,只不过不能满足需求,所以才扩展和自定义实现类。
默认日志实现类,是没有开启的。
阿里druid连接池
不仅仅是dubbo框架,其他框架基本上都是这个套路,就是如果要扩展的话,即实现自定义类,就实现拦截器,然后配置即可。基本上就是这2个步骤。
比如,阿里druid连接池,我们想要实现监控连接池状态的功能,具体功能如下
- 获取数据库连接耗时
- 活跃连接数量占连接池总数量的比例是否超过阈值
然后,上面2个监控指标,如果超过阈值就告警。
实现步骤
这里只讲spi实现和配置,监控和告警是基于美团cat实现的,和spi无关,这里不讲。
- 自定义实现类
实现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 {
- 配置
指定实现类
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>