SPI

1 篇文章 0 订阅

SPI

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它
来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦,
使得第三方服务模块的装配控制逻辑与调用者的业务代码分离

Java SPI

在这里插入图片描述

Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通
过SPI机制中约定好的信息进行查询相应的接口实现。

Java SPI遵循如下约定

  1. 当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全
    限定名”为命名的文件,内容为实现类的全限定名;
  2. 接口实现类所在的jar包放在主程序的classpath中;
  3. 主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下
    的配置文件找到实现类的全限定名,把类加载到JVM;
  4. SPI的实现类必须携带一个无参构造方法;

Java SPI 示例:

public interface IHelloService {
    String sayHello();
}

public class HumanHelloServiceImpl implements IHelloService {

    @Override
    public String sayHello() {
        return "你好";
    }
}

public class CatHelloServiceImpl implements IHelloService {

    @Override
    public String sayHello() {
        return "Mio Mio";
    }
}

在这里插入图片描述

import com.james.demo.IHelloService;

import java.util.ServiceLoader;


public class HelloServiceTest {

    public static void main(String[] args) {
        ServiceLoader<IHelloService> iHelloServices = ServiceLoader.load(IHelloService.class);
        for (IHelloService iHelloService :iHelloServices) {
            System.out.println(iHelloService.getClass().getName() + ":"+ iHelloService.sayHello());
        }

    }
}

Dubbo SPI

dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。
比如比较常见的协议,负载均衡,都可以通过SPI的方式进行定制化,自己扩展。Dubbo中已经存在的
所有已经实现好的扩展点。

Dubbo中的SPI

翻看Dubbo的jar包,其大量的SPI在META-INF 下的internal目录下
在这里插入图片描述
比如
org.apache.dubbo.cache.CacheFactory

threadlocal=org.apache.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=org.apache.dubbo.cache.support.lru.LruCacheFactory
jcache=org.apache.dubbo.cache.support.jcache.JCacheFactory
expiring=org.apache.dubbo.cache.support.expiring.ExpiringCacheFactory

org.apache.dubbo.common.serialize.Serialization

fastjson=org.apache.dubbo.common.serialize.fastjson.FastJsonSerialization
fst=org.apache.dubbo.common.serialize.fst.FstSerialization
hessian2=org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization
native-hessian=org.apache.dubbo.serialize.hessian.Hessian2Serialization
java=org.apache.dubbo.common.serialize.java.JavaSerialization
compactedjava=org.apache.dubbo.common.serialize.java.CompactedJavaSerialization
nativejava=org.apache.dubbo.common.serialize.nativejava.NativeJavaSerialization
kryo=org.apache.dubbo.common.serialize.kryo.KryoSerialization
kryo2=org.apache.dubbo.common.serialize.kryo.optimized.KryoSerialization2
avro=org.apache.dubbo.common.serialize.avro.AvroSerialization
protostuff=org.apache.dubbo.common.serialize.protostuff.ProtostuffSerialization

gson=org.apache.dubbo.common.serialize.gson.GsonSerialization

protobuf-json=org.apache.dubbo.common.serialize.protobuf.support.GenericProtobufJsonSerialization
protobuf=org.apache.dubbo.common.serialize.protobuf.support.GenericProtobufSerialization

org.apache.dubbo.registry.client.ServiceDiscovery

file=org.apache.dubbo.registry.client.FileSystemServiceDiscovery
zookeeper=org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery
consul=org.apache.dubbo.registry.consul.ConsulServiceDiscovery
etcd3=org.apache.dubbo.registry.etcd.EtcdServiceDiscovery

eureka=org.apache.dubbo.registry.eureka.EurekaServiceDiscovery
nacos=org.apache.dubbo.registry.nacos.NacosServiceDiscovery

org.apache.dubbo.rpc.cluster.LoadBalance

random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
shortestresponse=org.apache.dubbo.rpc.cluster.loadbalance.ShortestResponseLoadBalance

Dubbo SPI遵循如下约定

  1. 定义的接口,必须加上org.apache.dubbo.common.extension.SPI注解,当服务提供者提供了接口的一种具体实现后,在META-INF/duboo目录下创建一个以“接口全限定名”为命名的文件,内容为标签=实现类的全限定名;
  2. 接口实现类所在的jar包放在主程序的classpath中;
  3. 主程序通过org.apache.dubbo.common.extension.ExtensionLoader 获取扩展加载器,它通过扫描META-INF/duboo目录下的配置文件找到实现类的全限定名,遍历所有的支持的扩展点(extensionLoader.getSupportedExtensions()),把类加载到JVM;
  4. 通过扩展点获取具体的实现

Dubbo SPI示例:

接口

import org.apache.dubbo.common.extension.SPI;
@SPI
public interface IHelloService {
    String sayHello();
}

实现类

public class CatHelloServiceImpl implements IHelloService {

    @Override
    public String sayHello() {
        return "Mio Mio";
    }
}

public class DogHelloServiceImpl implements IHelloService {

    @Override
    public String sayHello() {
        return "Wang Wang";
    }
}

public class HumanHelloServiceImpl implements IHelloService {

    @Override
    public String sayHello() {
        return "你好";
    }
}

扩展点 META-INF/dubbo
在这里插入图片描述
com.james.demo.IHelloService

cat=com.james.demo.impl.CatHelloServiceImpl
dog=com.james.demo.impl.DogHelloServiceImpl
human=com.james.demo.impl.HumanHelloServiceImpl

测试


import org.apache.dubbo.common.extension.ExtensionLoader;

import java.util.Set;
public class DubboSpiTest {

    public static void main(String[] args) {
        /**
         * 获取扩展加载器
         */
        ExtensionLoader<IHelloService> extensionLoader = ExtensionLoader.getExtensionLoader(IHelloService.class);
        /**
         * 遍历所有的支持的扩展点 META-INF.dubbo
         */
        Set<String> loadedExtensions = extensionLoader.getSupportedExtensions();

        for (String extension : loadedExtensions) {
            System.out.println(extension + ":" + extensionLoader.getExtension(extension).sayHello());
        }
        /**
         * cat:Mio Mio
         * dog:Wang Wang
         * human:你好
         */

    }
}

Dubbo中SPI的动态扩展(Adaptive)

Dubbo中的Adaptive功能,主要解决的问题是如何动态的选择具体的扩展点。通过
getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行
动态选择。 (dubbo中所有的注册信息都是通过URL的形式进行处理的。)这里同样采用相同的方式进行
实现。
org.apache.dubbo.common.extension.Adaptive
org.apache.dubbo.common.URL

接口:

api中的 HelloService 扩展如下方法, 与原先类似,在sayHello中增加 Adaptive 注解,并且在参数中
提供URL参数.注意这里的URL参数的类为 org.apache.dubbo.common.URL
其中@SPI(value = “human”)可以指定一个字符串参数,用于指明该SPI的默认实现。

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;


@SPI(value = "human")
public interface IHelloService {
    String sayHello();

    @Adaptive
    String sayHello(URL url);
}

实现类
与上面Service实现类代码相似,只需增加URL形参即可
如:

public class HumanHelloServiceImpl implements HelloService {

    @Override
    public String sayHello() {
        return "你好";
    }

    @Override
    public String sayHello(URL url) {
        return "你好";
    }
}

扩展点
在这里插入图片描述
测试



import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;


public class DubboSpiAdaptiveTest {

    public static void main(String[] args) {
        /**
         * 获取HelloService扩展加载器
         * 通过 getAdaptiveExtension 来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展
         * 点进行封装)。
         */
        HelloService helloServiceExtensionLoader = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getAdaptiveExtension();
        /**
         * 因为在这里只是临时测试,所以为了保证URL规范,前面的信息均为测试值即可,关键的点在于
         * hello.service 参数,这个参数的值指定的就是具体的实现方式。关于为什么叫
         * hello.service 是因为这个接口的名称,其中后面的大写部分被dubbo自动转码为 . 分割。
         *
         *
         * 调用时通过参数中增加 URL 对象来实现动态的扩展点使用
         */
        System.out.println(helloServiceExtensionLoader.sayHello(URL.valueOf("test://localhost/hello?hello.service=dog")));
        System.out.println(helloServiceExtensionLoader.sayHello(URL.valueOf("test://localhost/hello?hello.service=cat")));
        System.out.println(helloServiceExtensionLoader.sayHello(URL.valueOf("test://localhost/hello?hello.service=human")));
        /**
         * 如果URL没有提供该参数,则该方法会使用默认在 SPI 注解中声明的实现。
         * 这里是使用默认扩展点, 即在接口@SPI中配置的默认值@SPI(value = "human")
         */
        System.out.println(helloServiceExtensionLoader.sayHello(URL.valueOf("test://localhost/hello")));

    }
}

上面测试的注意点:

  • 在这里只是临时测试,所以为了保证URL规范,前面的信息均为测试值即可,关键的点在于
    hello.service 参数,这个参数的值指定的就是具体的实现方式。为什么是hello.service? 是因为这个接口的名称,其中后面的大写部分被dubbo自动转码为 . 分割。
  • 通过 getAdaptiveExtension 来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展
    点进行封装)。
  • 调用时通过参数中增加 URL 对象来实现动态的扩展点使用。
  • 如果URL没有提供该参数,则该方法会使用默认在 SPI 注解中声明的实现

Dubbo为什么不用Java的SPI

1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加 载,会很浪费资源 
2. 如果有扩展点加载失败,则所有扩展点无法使用 
3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值