Java中SPI机制

 

什么是SPI

被广大程序员所熟知的就是API,API叫做"Application Programming Interface",即应用程序接口,是框架对外提供的能力的接口。SPI叫做"Service Provider Interface",即服务提供接口,是用于扩展框架能力的。

 

SPI有什么用

我们长听说面向接口编程,那么面向接口编程的有点是什么呢?面向接口编程的优点是不需要关注实现逻辑和细节,完全可插拔。

 

JAVA中SPI实现分析

下图是SPI使用的一个流程:

从图中可以看出最重要的是实现加载服务实现。JAVA SPI接口的实现实际上是"接口+策略模式+配置文件"

JAVA SPI实现要求以及服务加载:

  • 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以接口全限定名为命名的文件,内容为实现类的全限定名,每行一个实现。具体做法就是在resources下创建META-INF/services目录
  • 接口实现类所在的jar包放在主程序的classpath中
  • 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。main函数中调用ServiceLoder加载接口类。
  • SPI的实现类必须携带一个不带参数的构造方法

ServiceLoder.java加载实现机制:

    //指定目录加载的资源
    private static final String PREFIX = "META-INF/services/";
    // 需要被加载的接口
    private final Class<S> service;
    // 类加载器
    private final ClassLoader loader;
    // 访问控制
    private final AccessControlContext acc;
    //缓存providers,按实例化的顺序排列
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 懒查找迭代器
    private LazyIterator lookupIterator;

  //这里会发现一个问题:如果想要热加载某个类的实现的话,会造成全局的重新扫描,比较浪费资源  
  public void reload() {
        //每次调用都会清除掉之前加载的实现
        providers.clear();
        //重新加载loader以及实现
        lookupIterator = new LazyIterator(service, loader);
    }

   // 调用load接口
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
      Class<S> service;
      ClassLoader loader;
      Enumeration<URL> configs = null;
      Iterator<String> pending = null;
      String nextName = null;

      //获取配置文件中的信息
      private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
               //实例化接口实现
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                //将实例放在缓存
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

 

 

SPI的优缺点

优点:

模块解耦,系统扩展性增强

 

缺点:

使用的时候,会重新加载所有实现类,资源浪费。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值